home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / archival / mirror-2.1 / mirror.pl < prev    next >
Encoding:
Perl Script  |  1993-06-29  |  72.4 KB  |  3,083 lines

  1. #!/usr/bin/perl
  2. # Make local directories mirror images of a remote sites
  3. # By Lee McLoughlin <lmjm@doc.ic.ac.uk>
  4. #  You can do what you like with this except claim that you wrote it or
  5. #  give copies with changes not approved by Lee.  Neither Lee nor any other
  6. #  organisation can be held liable for any problems caused by the use or
  7. #  storage of this package.
  8. #
  9. # $Id: mirror.pl,v 2.1 1993/06/28 14:59:00 lmjm Exp lmjm $
  10. # $Log: mirror.pl,v $
  11. # Revision 2.1  1993/06/28  14:59:00  lmjm
  12. # Full 2.1 release
  13. #
  14. #
  15.  
  16. # Default settings file loaded from a directory in PERLLIB
  17. $defaults_file = 'mirror.defaults';
  18. $load_defaults = 1;
  19.  
  20. # Try to find the default localation of various programs via
  21. # the users PATH then using $extra_path
  22. $extra_path = '/usr/local/bin:/usr/new/bin:/usr/public/bin:/usr/ucb:/usr/bin:/bin:/etc:/usr/etc:/usr/local/etc:';
  23.  
  24. # If compressing a local file to send need somewhere to store the temp
  25. # compressed version.
  26. $big_temp = '/var/tmp';
  27.  
  28. # NOTE:
  29. #  It is not an error for a program not to be found in the path as the user
  30. # may be setting it as part of the package details or defaults.
  31.  
  32. # Used by the save_deletes option
  33. $mv_prog = 'mv -f';
  34.  
  35. # compress must be able to take the -d arg to cause it to uncompress.
  36. $sys_compress_prog = &find_prog( 'compress' ) ||
  37.     die "No compress command in path\n";
  38. $sys_compress_suffix = 'Z';
  39. # Like compress gzip must be able to take -d
  40. if( $gzip_prog = &find_prog( 'gzip' ) ){
  41.     # Force maximum compression with gzip
  42.     $gzip_level = ' -9';
  43.     $gzip_prog .= $gzip_level;
  44.     $gzip_suffix = 'gz';
  45.     $old_gzip_suffix = 'z';
  46. }
  47.  
  48. # A mail program that can be called as: "$mail_prog person_list'
  49. # Can be overridden with the mail_prog keyword.
  50. # If you use $mail_subject to pass extra arguments then remember that
  51. # the mail program will need to know how to handle them.
  52. $mail_prog = &find_prog( 'mailx' );
  53. if( ! $mail_prog ){
  54.     $mail_prog = &find_prog( 'Mail' );
  55. }
  56. if( ! $mail_prog ){
  57.     $mail_prog = &find_prog( 'mail' );
  58. }
  59. $current_mail_to = '';        # Keep track of who mail is being sent to.
  60.  
  61. # Used to remove directory heirarchies.  This programs is passed the -rf
  62. # arguments.
  63. $rm_prog = &find_prog( 'rm' );
  64.  
  65. # Generate checksums
  66. $sum_prog = &find_prog( 'sum' );
  67.  
  68. # SPECIAL NOTE: This is eval'd, so DONT put double-quotes (") in it.
  69. # You can get local variables to appear as in the second example:
  70. $mail_subject = '-s \'mirror update\'';
  71. # $mail_subject = ' -s \'mirror update of $package\'';
  72.  
  73. # When scanning the local directory, how often to prod the remote
  74. # system to keep the connection alive
  75. $prod_interval = 60;
  76.  
  77. # Put the directory that mirror is actually in at the start of PERLLIB.
  78. $dir = &real_dir_from_path( $0 );
  79. unshift( @INC, $dir );
  80.  
  81. # Make sure that your PERLLIB environment variable can get you
  82. # all these
  83. require 'ftp.pl';
  84. require 'lsparse.pl';
  85. require 'dateconv.pl';
  86. require 'socket.ph';
  87.  
  88. # Find some local details
  89. chop( $home = `pwd` );
  90. chop( $hostname = `hostname` );
  91. if( $hn = (gethostbyname( "$hostname" ))[ 0 ] ){
  92.     $hostname = $hn;
  93. }
  94.  
  95. # Some systems hold the username in $USER, some in $LOGNAME.
  96. $me = $ENV{'USER'} || $ENV{'LOGNAME'};
  97.  
  98. $retry_pause = 60;    # Pause before retrying
  99. $squished = '\.(Z|z|gz)$';# Files matching this pattern are usually compressed
  100.  
  101. # Remote directory parsing fail if not given input every readtime seconds.
  102. $parse_time = 600;
  103.  
  104. # Timeout are not fatal unless you get more than this number of them
  105. $max_timeouts = 20;
  106.  
  107. # If connected to a site then this holds the site name.
  108. $connected = '';
  109.  
  110. # Umask setting.
  111. $curr_umask = sprintf( "0%o", umask );
  112.  
  113. # mapping from a pathname to a number - just to make the keys to assoc arrays
  114. # shorter.
  115. $map_init = 1;    # just so I know 0 is invalid
  116.  
  117. $tmp = "/tmp";
  118. @assocs = ( 'local_map', 'remote_map' );
  119.  
  120. # Create a reasonable set of defaults
  121. $default{ 'package' } = '';    # should be a unique handle for the "lump" to be mirrored
  122. $default{ 'comment' } = '';    # General comment used in report
  123. $default{ 'skip' } = '';    # If set then skip this entry giving value as reason
  124. $default{ 'site' } = '';    # site to connect to
  125. $default{ 'remote_dir' } = '';    # remote directory to mirror
  126. $default{ 'local_dir' } = '';    # local directory to copy into
  127. $default{ 'remote_user' } = 'anonymous';  # the remote guest account name
  128. $default{ 'remote_password' } = "$me@$hostname";
  129. $default{ 'get_patt' } = ".";    # regex of pathnames to retrieve
  130. $default{ 'exclude_patt' } = ''; # regex of pathnames to ignore
  131. $default{ 'update_local' } = 0;    # Don't just update local dirs
  132. $default{ 'local_ignore' } = ''; # regex of local pathnames to totally ignore
  133. $default{ 'do_deletes' } = 0;    # delete dest files if not in src tree
  134. $default{ 'delete_excl' } = ''; # regex of local pathnames to ignore when deleting
  135. $default{ 'delete_patt' } = '.';# delete only files which match this pattern
  136. $default{ 'delete_get_patt' } = 0;# true: set delete_patt to get_patt
  137. $default{ 'save_deletes' } = 0;    # save local files if not in remote
  138. $default{ 'save_dir' } = 'Old';    # directory in which to create tree for keeping
  139.                 # files no longer in remote
  140. $default{ 'max_delete_files' } = 20; # Any more than this and DONT delete
  141. $default{ 'max_delete_dirs' } = 10; # Any more than this and DONT delete
  142. $default{ 'max_days' } = 0;    # Ignore age of file
  143. $default{ 'split_max' } = 0;    # Files > this size can be split up.
  144. $default{ 'split_patt' } = '';  # Files must match this pattern to be split
  145. $default{ 'split_chunk' } = 100 * 1024; # Size of split-up chunks
  146. $default{ 'ls_lR_file' } = '';    # remote file containing ls-lR - else use remote ls
  147. $default{ 'local_ls_lR_file' } = ''; # local file containing ls-lR
  148.                 # useful when first copying a large remote package
  149. $default{ 'recursive' } = 1;    # true indicates to do recursive processing
  150. $default{ 'recurse_hard' } = 0;    # true indicates have to cwd+ls for each remote
  151.                 # subdirectory - AVOID wherever possible.
  152. $default{ 'flags_recursive' } = '-lRat'; # Flags passed to remote dir
  153. $default{ 'flags_nonrecursive' } = '-lat'; # Flags passed to remote dir
  154. $default{ 'name_mappings' } = '';# remote to local pathname mappings
  155.                  # (eg s:old:new)
  156. $default{ 'external_mapping' } = '';# remote to local mapping by external routine
  157. $default{ 'get_newer' } = 1;    # get remote file if its date is newer than local
  158. $default{ 'get_size_change' } = 1; # get the file if size if different than local
  159. $default{ 'compress_patt' } = ''; # compress files matching this pattern
  160. $default{ 'compress_excl' } = $squished; # dont compress regexp (case insensitive)
  161. $default{ 'compress_prog' } = $sys_compress_prog; # Program to compress files.
  162. $default{ 'compress_suffix' } = $sys_compress_suffix; # Suffix on compressed files
  163. $default{ 'compress_conv_patt' } = '(\.Z|\.taz)$';
  164.     # compress->gzip files matching this pattern
  165. $default{ 'compress_conv_expr' } = 's/\.Z$/.gz/;s/\.taz$/.tgz/';
  166.     # perl expressions to convert names of files from compress->gzip
  167. $default{ 'force_times' } = 1;    # Force local file times to match the original
  168. $default{ 'retry_call' } = 1;    # Retry the call if it fails first time
  169. $default{ 'update_log' } = '';    # Filename where an update report is to be kept
  170. $default{ 'mail_to' } = '';    # Mail a report to these addresses
  171. $default{ 'user' } = '';    # UID/user name to give to local pathnames
  172. $default{ 'group' } = '';    # GID/group name to give to local pathnames
  173. $default{ 'file_mode' } = 0444;    # Mode to give files created locally
  174. $default{ 'dir_mode' } = 0755;    # mode to give directories created locally
  175. $default{ 'timeout' } = 120;    # timeout ftp requests after this many seconds
  176. $default{ 'ftp_port' } = 21;    # port number of remote ftp daemon
  177. $default{ 'proxy' } = 0;    # normally use regular ftp
  178. $default{ 'proxy_ftp_port' } = 4514; # default from Sun
  179. $default{ 'proxy_gateway' } = $ENV{ 'INTERNET_HOST' };    # used if($proxy) 
  180. $default{ 'mode_copy' } = 0;    # true indicates to copy the mode bits
  181. $default{ 'interactive' } = 0;    # noninteractive copy default
  182. $default{ 'text_mode' } = 0;    # transfer in binary mode by default
  183. $default{ 'force' } = 0;    # don't force by default
  184. $default{ 'get_file' } = 1;    # perform get, not put by default
  185. $default{ 'verbose' } = 0;    # Verbose messages
  186. $default{ 'remote_fs' } = 'unix'; # Remote filestore
  187.     # Other posibilies dls, netware and vms
  188. $default{ 'vms_keep_versions' } = 1; # Keep multiple VMS versions
  189. $default{ 'vms_xfer_text' } = 'readme$|info$|listing$|\.c$';
  190.                 # pattern of VMS files to xfer in TEXT mode
  191.                 # (Case insensitive)
  192. $default{ 'delete_source' } = 0;# delete source after xfer (default = NO!!!)
  193. $default{ 'disconnect' } = 0;    # Force close at end of package EVEN if
  194.                 # next package is to the same site
  195. $default{ 'mail_prog' } = $mail_prog; # the mail program (see $mail_prog)
  196. $default{ 'mail_subject' } = $mail_subject; # Subject passed to mail_prog
  197. $default{ 'hostname' } = $hostname; # The LOCAL hostname
  198. $default{ 'umask' } = 07000; # DONT allow setuid things by default
  199. # If mirroring a VERY large directory then it is best to put the assoc
  200. # arrays in files (use command line switch -F. to turn on).
  201. $default{ 'use_files' }  = 0;
  202. # Use local time NOT gmt to timestamp files.
  203. # The original mirror got it wrong you should be using localtime
  204. # This variable is only here to allowed packages to be switched over one by
  205. # one.  (See also the -T flag.)
  206. $default{ 'use_timelocal' }  = 1;
  207. # Used for group and gpass.  (As in ftp.1 site group/gpass commands.)
  208. $default{ 'remote_group' } = '';
  209. $default{ 'remote_gpass' } = '';
  210. # Set the remote idle timer to this
  211. $default{ 'remote_idle' } = '';
  212. # prevent symlinks to non-existant files
  213. $default{ 'make_bad_symlinks' } = 0;
  214. # Follow symlinks to pathnames matching this regexp.
  215. $default{ 'follow_local_symlinks' } = '';
  216.  
  217.  
  218. @boolean_values = ( 'get_newer', 'get_size_change', 'do_deletes',
  219.     'update_local',    'force_times', 'retry_call', 'recursive',
  220.     'mode_copy', 'disconnect', 'interactive', 'text_mode',
  221.     'force', 'get_file', 'verbose', 'proxy', 'delete_get_patt',
  222.     'delete_source', 'save_deletes', 'use_files', 'use_timelocal',
  223.     'make_bad_symlinks' );
  224. %boolean_values = ();
  225. &set_assoc_from_array( *boolean_values );
  226.  
  227. @regexp_values = ( 'get_patt', 'exclude_patt', 'local_ignore',
  228.           'delete_patt', 'delete_excl', 'split_patt', 'save_deletes',
  229.           'compress_patt', 'compress_excl', 'compress_conv_patt' );
  230.  
  231. #
  232. # message levels used by &msg( level, msg )
  233. # if you call msg as &msg( msg ) the level is presumed to be just $pr.
  234. $pr = 0;    # Always print out messages
  235. $log = 1;    # push this messages onto @log
  236.  
  237. #
  238. # Exit status
  239. $exit_status = 0;
  240. $exit_status_xfers = 0;
  241.  
  242. # "#defines" for the above
  243. $exit_xfers = 16;  # Add this to the exit code to show xfers took place
  244. $exit_ok = 0;
  245. $exit_fail = 1;
  246. $exit_fail_noconnect = 2;
  247.  
  248. # -d        Turn on debugging - more -d's means more debugging.
  249. # -ppattern    Just do packages matching pattern.
  250. # -Rpattern    Skip till the first package name matches pattern then do all.
  251. #        it and following packages.
  252. # -n        Do nothing, just show what would be done.
  253. # -F        Use files for assoc arrays (see also the variable use_files).
  254. # -gsite:path
  255. #        Get all files on given site.  If path matches .*/.+ then
  256. #        it is the name of the directory and the last part is the
  257. #        pattern of filenames to get.  If path matches .*/ then
  258. #        it is the name of a directory and all its contents are retrieved.
  259. #        Otherwise path is the pattern to be used in '/'.
  260. # -r        Same as "-krecursive=false".
  261. # -kvar=val    set variable to value.
  262. # -uusername    Same as "-kremote_user=username", prompts for remote_password.
  263. # -v        Print version and exit.
  264. # -T        Dont transfer just force local timestamps to match remote.
  265. # -N        Don't load mirror.defaults.
  266. # -L        Generate a pretty list of what is being mirrored.
  267. # -m         Same as "-kmode_copy=true".
  268.  
  269. # -Cconfig_file
  270. # -P         Same as "-kget_file=false -kinteractive=true".
  271. # -G        Same as "-kget_file=true -kinteractive=true".
  272. # -t         Same as "-ktext_mode=true".
  273. # -f        Same as "-kforce=true".
  274. # -sSITENAME    Same as "-ksite=SITENAME.
  275. # -ULOGFILE    Set the upload log to LOGILE - if none given uses
  276. #        the file $home/upload_log.$mday.$mon.$year
  277.  
  278. # -DUMP        Dump perl - to be later undumped --  THIS DOES NOT YET WORK!!!
  279.  
  280. sub msg_version
  281. {
  282.     &msg( '$Id: mirror.pl,v 2.1 1993/06/28 14:59:00 lmjm Exp lmjm $' . "\n" );
  283. }
  284.  
  285. parse_args:
  286. while( $ARGV[ 0 ] =~ /^-/ ){
  287.     local( $arg ) = shift;
  288.  
  289.     if( $arg eq '-d' ){
  290.         if( $debug == 2 ){
  291.             &msg_version();
  292.         }
  293.         $| = 1;
  294.         $debug++;
  295.         next;
  296.     }
  297.  
  298.     if( $arg =~ /^-(p)(.*)/ || $arg =~ /^-(R)(.*)/ ){
  299.         local( $flag, $p ) = ($1, $2);
  300.         if( $flag eq 'R' ){
  301.             # Skip all packages till a match is made
  302.             # then process ALL further packages
  303.             $skip_till = 1;
  304.         }
  305.         if( ! $p ){
  306.             # Must be -p/-R space arg
  307.             $p = shift;
  308.         }
  309.         if( $p !~ /[a-zA-Z0-9]/ ){
  310.             die "Invalid package name to -p of: $p\n";
  311.             next;
  312.         }
  313.         # Only mirror the named packages
  314.         $do_packages{ $p } = 1;
  315.         $limit_packages = 1;
  316.         next;
  317.     }
  318.  
  319.     if( $arg eq '-n' ){
  320.         # Do nothing - just show what would be done
  321.         $dont_do = 1;
  322.         $debug += 2;
  323.         $| = 1;
  324.         next;
  325.     }
  326.  
  327.     if( $arg eq '-F' ){
  328.         # Use files for the dir listings assoc lookups
  329.         $use_files = 1;
  330.         $command_line{ 'use_files' } = 1;
  331.         next;
  332.     }
  333.  
  334.     if( $arg eq '-T' ){
  335.         # Don't actually get any files but just force
  336.         # local timestamps to be the same on the remote system
  337.         $timestamp = 1;
  338.         $command_line{ 'force_times' } = 'true';
  339.         next;
  340.     }
  341.  
  342.     if( $arg =~ /^-g(.*)$/ ){
  343.         # the next arg is the site:path to get
  344.         local( $site_path ) = $1;
  345.  
  346.         if( ! $site_path ){
  347.             # Must be -g space arg
  348.             $site_path = shift;
  349.         }
  350.         
  351.         # DONT use the system defaults!
  352.         $load_defaults = 0;
  353.         
  354.         # This is probably interactive so print interactively
  355.         $| = 1;
  356.         
  357.         if( $site_path =~ /(.*):(.*)?/ ){
  358.             local( $site, $path ) = ($1, $2);
  359.             push( @get_sites, $site );
  360.             # Find the directory and files
  361.             if( $path =~ m|^(.*)/([^/]*)$| ){
  362.                 if( $1 eq '' ){
  363.                     push( @get_paths, '/' );
  364.                 }
  365.                 else {
  366.                     push( @get_paths, $1 );
  367.                 }
  368.                 if( $2 eq '' ){
  369.                     push( @get_paths, $1 );
  370.                     push( @get_patt, '.' );
  371.                 }
  372.                 else {
  373.                     push( @get_patt, "^$2$" );
  374.                 }
  375.             }
  376.             else {
  377.                 push( @get_paths, '.' );
  378.                 push( @get_patt, "^$path$" );
  379.             }
  380.         }
  381.         else {
  382.             die "expected -gsite:path got $arg";
  383.         }
  384.         next;
  385.     }
  386.  
  387.     if( $arg eq "-r" ){
  388.         # no recursive copy
  389.         $command_line{ 'recursive' } = 0;
  390.         next;
  391.     }
  392.  
  393.     if( $arg =~ /^-k(.*)=(.*)/ ){
  394.          # set the keyword = value
  395.         if( !defined( $default{ "$1" } ) ){
  396.             warn "Invalid keyword $1\n";
  397.         } else {
  398.             $command_line{ "$1" } = $2;
  399.         }
  400.         next;
  401.     }
  402.  
  403.     if( $arg =~ /^-u(.*)/ ){
  404.         local( $user ) = $1;
  405.  
  406.         if( ! $user ){
  407.             # must be -u space user
  408.             $user = shift;
  409.         }
  410.  
  411.         # override the user name
  412.             $command_line{ 'remote_user' } = $user;
  413.         # and ask for a password
  414.         $command_line{ 'remote_password' } = &get_passwd( $user );
  415.         next;
  416.     }
  417.  
  418.     if( $arg eq '-N' ){
  419.         $load_defaults = 0;
  420.         next;
  421.     }
  422.  
  423.     if( $arg eq '-v' ){
  424.         &msg_version();
  425.         exit( 0 );
  426.     }
  427.  
  428.         if( $arg eq '-L' ){
  429.                 # Generate a pretty list of what is being mirrored
  430.                 $pretty_print = 1;
  431.                 next;
  432.         }
  433.  
  434.         if( $arg eq '-m' ){
  435.                 # propagate the mode
  436.         $command_line{ 'mode_copy' } = 'true';
  437.         next;
  438.         }
  439.  
  440.     # Old command line interface flags
  441.     if( $arg =~ /^-C(.*)/ ){
  442.         # specify the config file
  443.         local( $c ) = $1;
  444.         if( $c !~ /./ ){
  445.             die "Must give config file name -Cname ($arg)\n";
  446.         }
  447.         # Only mirror the named packages
  448.             push( @config_files, $c);
  449.         next;
  450.     }
  451.  
  452.         if( $arg eq '-P' ){
  453.                 # put files
  454.         $command_line{ 'get_file' } = 'false';
  455.         $command_line{ 'interactive' } = 'true';
  456.         next;
  457.         }
  458.  
  459.         if( $arg eq '-G' ){
  460.                 # get files
  461.         $command_line{ 'get_file' } = 'true';
  462.         $command_line{ 'interactive' } = 'true';
  463.         next;
  464.         }
  465.  
  466.         if( $arg eq '-t' ){
  467.                 # set the file mode to text
  468.         $command_line{ 'text_mode' } = 'true';
  469.         next;
  470.         }
  471.  
  472.         if( $arg eq '-f' ){
  473.                 # force file transfers irregardless of date/size matches
  474.         $command_line{ 'force' } = 'true';
  475.         next;
  476.         }
  477.  
  478.     if( $arg =~ /^-s(.*)/ ){
  479.         # override the site name
  480.         $command_line{ 'site' } = $1;
  481.         next;
  482.     }
  483.  
  484.     if( $arg =~ /^-U(.*)/ ){
  485.         $upload_log = $1;
  486.         if( $upload_log eq '' ){
  487.             local( $sec,$min,$hour,$mday,$mon,$year,
  488.                 $wday,$yday,$isdst ) 
  489.                 = localtime( time );
  490.             $mon++;
  491.             $upload_log = "$home/upload_log.$mday.$mon.$year";
  492.         }
  493.             
  494.         next;
  495.     }
  496.  
  497.     if( $arg eq '-DUMP' ){
  498.         # THIS DOES NOT YET WORK!!!!!
  499.         $dumped_version = 1;
  500.         warn "Dumping perl\n";
  501.         dump parse_args;
  502.     }
  503.  
  504.     warn "Unknown arg $arg, skipping\n";
  505. }
  506.  
  507. # Handle multi-line buffers in a sane way
  508. $* = 1;
  509.  
  510. $interactive = $command_line{ 'interactive' };
  511.  
  512. if( ! $interactive ){
  513.     local( $c );
  514.  
  515.     # The remainder of ARGV are package names
  516.     foreach $c ( @ARGV ){
  517.         push( @config_files, $c );
  518.     }
  519. }
  520.  
  521. if( $interactive && $limit_packages){
  522.     die "Can not mix -p and interactive";
  523. }
  524.  
  525. $value{ 'remote_user' } = $default{ 'remote_user' };
  526. %value = ();
  527. &set_defaults();
  528.  
  529. if( $load_defaults ){
  530.     local( $dir, $mp );
  531.     foreach $dir ( @INC ){
  532.         local( $f ) = "$dir/$defaults_file";
  533.         if( -f $f ){
  534.             $mp = $f;
  535.             last;
  536.         }
  537.     }
  538.     if( $mp ){
  539.         &msg( "defaults from $mp\n" ) if $debug > 2;
  540.         splice( @config_files, 0, 0, $mp );
  541.     }
  542.     else {
  543.         warn "No $defaults_file found in perl library path\n";
  544.     }
  545. }
  546. elsif( $debug > 1 ){
  547.     &msg( "not loading $defaults_file\n" );
  548. }
  549.         
  550.  
  551. &interpret_config_files();
  552.  
  553. # Shut down any remaining ftp session
  554. &disconnect();
  555.  
  556. &msg( "All done, Exiting\n" ) if $debug;
  557. exit( $exit_status + $exit_status_xfers );
  558.  
  559.  
  560. $key = ''; # The current keyword
  561. $value = ''; # the value for the keyword
  562.  
  563. sub interpret_config_files
  564. {
  565.     local( $fname );
  566.  
  567.     if( $#get_sites >= 0 ){
  568.         while( $#get_sites >= 0 ){
  569.             $value{ 'site' } = pop( @get_sites );
  570.             $value{ 'remote_dir' } = pop( @get_paths );
  571.             $value{ 'get_patt' } = pop( @get_patt );
  572.             $value{ 'local_dir' } = '.';
  573.             $value{ 'remote_user' } = 'anonymous';
  574.             $exit_status = &do_mirror();
  575.         }
  576.         return;
  577.     }
  578.         
  579.  
  580.     if( $command_line{ 'interactive' } ){
  581.         # No config file to read
  582.         $value{ 'package' } = 'interactive';
  583.         $exit_status = &do_mirror();
  584.         return;
  585.     }
  586.  
  587.     # if no configuration files were specified use standard input
  588.     @ARGV = @config_files;
  589.     &interpret_config();
  590. }
  591.  
  592. sub interpret_config
  593. {
  594.     while( <> ){
  595.         # Ignore comment and blank lines
  596.         next if /^\s*#/ || /^\s*$/;
  597.         
  598.         &parse_line();
  599.         
  600.         # Is this a new package?
  601.         if( $value{ 'package' } && $key eq 'package' ){
  602.             # mirror the existing package
  603.             $exit_status = &do_mirror();
  604.             
  605.             # reset
  606.             &set_defaults();
  607.  
  608.             # Make sure I'm at the right place for <> to work!
  609.             chdir $home;
  610.         }
  611.         
  612.         if( $debug > 3 ){
  613.             &msg( "$key \"$value\"\n" );
  614.         }
  615.  
  616.         $value{ $key } = $value;
  617.  
  618.         # do an explicit close for each file so $. gets reset
  619.         if( eof( ARGV ) ){
  620.             if( $debug > 3 ){
  621.                 &msg( "-- end of config file \"$ARGV\"\n" );
  622.             }
  623.             close( ARGV );
  624.         }
  625.     }
  626.  
  627.     # Mirror the last package in the file
  628.     if( $value{ 'package' } ){
  629.         $exit_status = &do_mirror();
  630.     }
  631. }
  632.  
  633. # parse each line for keyword=value
  634. sub parse_line
  635. {
  636.     local( $eqpl );
  637.     local( $cont ) = '&';
  638.  
  639.     chop;
  640.     if( /^\s*([^\s=+]+)\s*([=+])(.*)?$/ ){
  641.         ($key, $eqpl, $value) = ($1, $2, $3);
  642.         # If the value ends in the continuation character then
  643.         # tag the next line on the end (ignoring any leading ws).
  644.         while( $value =~ /^(.*)$cont$/o && !eof ){
  645.             $_ = <>;
  646.             local( $v ) = $1;
  647.             if( /^\s*(.*)$/ ){
  648.                 $value = $v . $1;
  649.             }
  650.         }
  651.         if( $debug > 3 ){
  652.             &msg( "read: $key$eqpl$value\n" );
  653.         }
  654.     }
  655.     else {
  656.         warn "unknown input in \"$ARGV\" line $. of: $_\n";
  657.     }
  658.     if( ! defined( $default{ "$key" } ) ){
  659.         die "unknown keyword in \"$ARGV\" line $. of: $key\n";
  660.     }
  661.     if( $eqpl eq '+' ){
  662.         $value = $value{ $key } . $value;
  663.     }
  664. }
  665.  
  666. # Initialise the key values to the default settings
  667. sub set_defaults
  668. {
  669.     %value = %default;
  670. }
  671.  
  672. # Override the current settings with command line values
  673. sub command_line_override
  674. {
  675.     local( $key, $val, $overrides );
  676.  
  677.     while( ($key, $val) = each %command_line ){
  678.         $overrides++;
  679.         if( $boolean_values{ $key } ){
  680.             # a boolean value
  681.             $value{ $key } = &istrue( $val );
  682.         } else {
  683.             # not a boolean value
  684.             $value{ $key } = $val;
  685.         }
  686.     }
  687.  
  688.     if( $debug > 4 ){
  689.         if( $overrides ){
  690.             &pr_variables( "keywords after command line override\n" );
  691.         }
  692.         else {
  693.             &msg( "No command line overrides\n" );
  694.         }
  695.     }
  696. }
  697.  
  698. # set each variable $key = $value{ $key }
  699. sub set_variables
  700. {
  701.     local( $key, $val );
  702.  
  703.     while( ($key, $val) = each %value ){
  704.         # for things like passwords it is nice to have the
  705.         # real value in a file
  706.         if( $val =~ /^\<(.*)$/ ){
  707.             local( $val_name ) = $1;
  708.             open( VAL_FILE, $val_name ) ||
  709.                 die "can't open value file $val_name\n";
  710.             $val = <VAL_FILE>;
  711.             close( VAL_FILE );
  712.             chop $val if $val =~ /\n$/;
  713.         }
  714.  
  715.         if( $boolean_values{ $key } ){
  716.             # a boolean value
  717.             eval "\$$key = &istrue( $val )";
  718.         }
  719.         else {
  720.             # not a boolan value
  721.             # Change all \ to \\ since \'s will be escaped in
  722.             # the following string used in the eval.
  723.             $val =~ s/([^\\])(')/$1\\$2/g;
  724.             eval "\$$key = '$val'";
  725.         }
  726.         if( $key eq 'compress_prog' ){
  727.             if( $val eq 'compress' ){
  728.                 $compress_prog = $sys_compress_prog;
  729.                 $compress_suffix = $sys_compress_suffix;
  730.             }
  731.             elsif( $val eq 'gzip' ){
  732.                 if( ! $gzip_prog ){
  733.                     die "Trying to use gzip but not found in PATH\n";
  734.                 }
  735.                 $compress_prog = $gzip_prog;
  736.                 $compress_suffix = $gzip_suffix;
  737.             }
  738.             elsif( $debug > 2 ){
  739.                 &msg( "compress_prog not compress or gzip, presuming program name\n" .
  740.                       "- user must set compress_suffix\n" );
  741.             }
  742.             &upd_val( 'compress_prog' );
  743.             &upd_val( 'compress_suffix' );
  744.         }
  745.     }
  746.  
  747.     # Reset the umask if needed.
  748.     # Do it here to try and get it done as early as possible.
  749.     # If the user doesn't use octal umasks this will cause umask
  750.     # to be called again unnecessarily - but that is pretty cheap.
  751.     if( $umask && $umask != $curr_umask ){
  752.         local( $val ) = $umask;
  753.         $val = oct( $val ) if $val =~ /^0/;
  754.         umask( $val );
  755.         $curr_umask = sprintf( "0%o", umask );
  756.     }
  757. }
  758.  
  759. sub upd_val
  760. {
  761.     local( $key ) = @_;
  762.     if( $package eq 'defaults' ){
  763.         $default{ $key } = $value{ $key };
  764.     }
  765. }
  766.  
  767. sub pr_variables
  768. {
  769.     local( $msg ) = @_;
  770.     local( $nle ) = 60;
  771.     local( $out ) = 0;
  772.     local( $key, $val, $str );
  773.  
  774.     &msg( $msg );
  775.     &msg( "package=$package  $site:$remote_dir -> $local_dir\n\t" );
  776.  
  777.     for $key ( sort keys( %value ) ){
  778.         next if $key eq 'package' ||
  779.             $key eq 'site' ||
  780.             $key eq 'remote_dir' ||
  781.             # Don't show passwords when interactive
  782.             ($interactive && $key eq 'remote_password') ||
  783.             ($interactive && $key eq 'remote_gpass');
  784.         # Report the value in the actual variable
  785.         $val = eval "\$$key";
  786.         $str = "$key=\"$val\" ";
  787.         &msg( $str );
  788.         $out += length( $str );
  789.         # Output newlines when a line is full
  790.         if( $out > $nle ){
  791.             $out = 0;
  792.             &msg( "\n\t" );
  793.         }
  794.     }
  795.     &msg( "\n" );
  796. }
  797.  
  798. # Mirror the package, return exit_status
  799. sub do_mirror
  800. {
  801.     $package = $value{ 'package' };
  802.     
  803.     if( $package eq 'defaults' ){
  804.         # This isn't a real site - just a way to change the defaults
  805.         %default = %value;
  806.  
  807.         return $exit_ok;
  808.     }
  809.  
  810.     # Only do this package if given by a -Ppack argument
  811.     if( $limit_packages && ! $do_packages{ $package } ){
  812.         return;
  813.     }
  814.  
  815.     if( $skip_till ){
  816.         # Found a package so process all packages from now on
  817.         $skip_till = $limit_packages = 0;
  818.     }
  819.     
  820.     local( $exit_status ) = $exit_fail_noconnect;  # Presume the worse.
  821.     $timeouts = 0;
  822.  
  823.     # set things from the command line arguments
  824.     &command_line_override();
  825.  
  826.     # set each variable $key = $value{ $key }
  827.     &set_variables();
  828.  
  829.     if( $debug > 3 ){
  830.         &pr_variables( "\n" );
  831.     }
  832.     elsif( $package && ! $pretty_print ){
  833.         &msg( "package=$package $site:$remote_dir -> $local_dir\n");
  834.     }
  835.     
  836.     # Check out the regexps
  837.     local( $t ) = 'x';
  838.     foreach $r ( @regexp_values ){
  839.         local( $val ) = $value{ $r };
  840.         next if ! $val;
  841.         eval '$t =~ /$val/';
  842.         if( $@ ){
  843.             local( $err );
  844.             chop( $err = $@ );
  845.             &msg( "Problem with regexp $r ($err), skipping package\n\n" );
  846.             return $exit_status;
  847.         }
  848.     }
  849.  
  850.     # Don't bother if trying to mirror here!
  851.     if( !$interactive && !$force && ((gethostbyname( $site ))[0] eq $hostname) ){
  852.         &msg( "Skipping $site as it is this local site!\n\n" );
  853.         return $exit_ok;
  854.     }
  855.  
  856.     chdir $home;
  857.  
  858.     $max_age = 0;
  859.     if( $value{ 'max_days' } ne '0' ){
  860.         $max_age = time - ($value{ 'max_days' } * 24 * 60 * 60);
  861.         &msg( "max_age = $max_age\n" ) if $debug > 1;
  862.     }
  863.  
  864.     # pull in external code, if required
  865.     if( $external_mapping ){
  866.         &msg( "Loading external mapping from $external_mapping.\n" ) if $debug > 0 ;
  867.         do $external_mapping || die "Cannot load from $external_mapping";
  868.     }
  869.  
  870.     if( $debug ){
  871.         # Keep the ftp debugging lower than the rest.
  872.         &ftp'debug( $debug - 1);
  873.     }
  874.     else {
  875.         &ftp'debug( $verbose );
  876.     }
  877.  
  878.     if( $recurse_hard ){
  879.         $recursive = 1;
  880.     }
  881.  
  882.     if( ! $interactive ){
  883.         $ftp'showfd = 'STDOUT';
  884.     }
  885.     &ftp'set_timeout( $timeout );
  886.     &ftp'set_signals( "main'msg" );
  887.  
  888.     # Useful string in prints
  889.     $XFER = $get_file ? "get" : "put";
  890.  
  891.     # create the list of items to copy
  892.     @transfer_list = ();
  893.     if( $interactive ){
  894.         # copy the remainder of items from argv to the transfer list
  895.         while( @ARGV ){
  896.             # copy the local directory
  897.             if( @ARGV ){
  898.                 push( @transfer_list, shift( @ARGV ) );
  899.             } 
  900.     
  901.             # copy the remote directory
  902.             if( @ARGV ){
  903.                 push( @transfer_list, shift( @ARGV ) );
  904.             }
  905.             else {
  906.                 die "remote directory must be specified\n";
  907.             }
  908.     
  909.             # copy the pattern, if available
  910.             if( @ARGV ){
  911.                 push( @transfer_list, shift( @ARGV ) );
  912.             } else {
  913.                 push( @transfer_list, $default{ 'get_patt' } );
  914.             }
  915.         }
  916.     
  917.         if( $debug > 1 ){
  918.             local( @t );
  919.             @t  = @transfer_list;
  920.     
  921.             while( @t ){
  922.                 printf( "local_dir=%s remote_dir=%s patt=%s\n",
  923.                     shift( @t ), shift( @t ), shift( @t ) );
  924.             }
  925.         }
  926.     }
  927.     else {
  928.         push( @transfer_list, $local_dir );
  929.         push( @transfer_list, $remote_dir );
  930.         push( @transfer_list, $get_patt );
  931.         }
  932.         
  933.  
  934.     if( $update_local && $get_patt ){
  935.         if( $get_patt ne $default{ 'get_patt' } ){
  936.             &msg( "Cannot mix get_patt and update_local.  get_patt ignored\n" );
  937.         }
  938.         $get_patt = '';
  939.     }
  940.         
  941.     
  942.     if( !$site || (!$interactive && (!$local_dir || !$remote_dir)) ){
  943.         &msg( "Insufficient details for package to be fetched\n" );
  944.         &msg( "Must give at least: site, remote_user, remote_dir and local_dir\n\n" );
  945.         return $exit_status;
  946.     }
  947.  
  948.         if( $pretty_print ){
  949.                 # Don't actually mirror just print a pretty list
  950.                 # of what would be mirrored.  This is for mailing to
  951.                 # people
  952.         if( $skip ){
  953.             return $exit_ok;
  954.         }
  955.                 &msg( "$package  \"$comment\"\n" );
  956.                 &msg( "  $site:$remote_dir  -->  $local_dir\n\n" );
  957.                 return $exit_ok;
  958.         }
  959.  
  960.     if( $skip ){
  961.         &msg( "Skipping $site:$package because $skip\n\n" );
  962.         return $exit_ok;
  963.     }
  964.  
  965.     $split_max = &to_bytes( $split_max );
  966.     $split_chunk = &to_bytes( $split_chunk );
  967.  
  968.     if( $split_max && $split_max <= $split_chunk ){
  969.         &msg( "split_max <= split_chunk - skipping package\n" );
  970.         &msg( " $split_max <= $split_chunk\n\n" );
  971.         return $exit_status;
  972.     }
  973.  
  974.     if( $split_chunk && ($split_chunk & 511) ){
  975.         &msg( "split_chunk bad size - skipping package\n" );
  976.         &msg( " $split_chunk should be a multiple of 512 bytes\n\n" );
  977.         return $exit_status;
  978.     }
  979.  
  980.     local( $con ) = &connect();
  981.     if( $con <= 0 ){
  982.         &msg( "Cannot connect, skipping package\n" );
  983.         &disconnect();
  984.         &msg( "\n" );
  985.         return $exit_status;
  986.     }
  987.  
  988.     if( $con == 1 ){
  989.         &msg( "login as $remote_user\n" ) if $debug > 1;
  990.         $curr_remote_user = $remote_user;
  991.         if( ! &ftp'login( $remote_user, $remote_password ) ){
  992.             &msg( "Cannot login, skipping package\n" );
  993.             &disconnect();
  994.             &msg( "\n" );
  995.             return $exit_status;
  996.         }
  997.         $can_restart = (&ftp'restart(0) == 1);
  998.         if( $debug > 1 ){
  999.             &msg( "Can " . $can_restart ? '' : "not " . "do restarts\n" );
  1000.  
  1001.         }
  1002.     
  1003.         if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
  1004.             &msg( "Cannot set type\n" );
  1005.         }
  1006.     }
  1007.     else {
  1008.         # Already connected to this site - so no need to login again
  1009.         &msg( "Already connected to site $site\n" ) if $debug;
  1010.     }
  1011.  
  1012.     $exit_status = $exit_fail; # ok this is now the worse case
  1013.  
  1014.     # Mirror thinks in terms of Unix pathnames.
  1015.     # Ask ftp.pl to map any remote name it is about to use by
  1016.     # setting the namemap functions.
  1017.     if( $remote_fs =~ /vms/i ){
  1018.         $vms = 1;
  1019.         &ftp'set_namemap( "main'unix2vms", "main'vms2unix" );
  1020.     }
  1021.     else {
  1022.         # No mapping necessary
  1023.         &ftp'set_namemap( '' );
  1024.     }
  1025.  
  1026.     if( ! $get_file ){
  1027.         local( @rhelp ) = &ftp'site_commands();
  1028.         $remote_has_chmod = grep( $_ eq 'CHMOD', @rhelp);
  1029.         $remote_has_idle = grep( $_ eq 'IDLE', @rhelp);
  1030.         if( $debug > 2 ){
  1031.             &msg( "remote site " . ($remote_has_chmod ? "has" : "hasn't") . " got chmod\n" );
  1032.             &msg( "remote site " . ($remote_has_idle ? "has" : "hasn't") . " got idle\n" );
  1033.         }
  1034.     }
  1035.     
  1036.     if( $remote_has_idle && $remote_idle ){
  1037.         if( ! &ftp'quote( "site idle $remote_idle" ) ){
  1038.             &msg( "Cannot set remote idle\n" );
  1039.         }
  1040.         elsif( $debug > 2 ){
  1041.              &msg( "remote idle has been set to $remote_idle\n" );
  1042.         }
  1043.     }
  1044.  
  1045.     if( $remote_group ){
  1046.         if( ! &ftp'quote( "site group $remote_group" ) ){
  1047.             &msg( "Cannot set remote group\n" );
  1048.         }
  1049.         elsif( $debug > 2 ){
  1050.              &msg( "remote group has been set to $remote_group\n" );
  1051.         }
  1052.     }
  1053.     
  1054.     if( $remote_gpass ){
  1055.         if( ! &ftp'quote( "site gpass $remote_gpass" ) ){
  1056.             &msg( "Cannot set remote gpass\n" );
  1057.         }
  1058.         elsif( $debug > 2 ){
  1059.              &msg( "remote gpass has been set\n" );
  1060.         }
  1061.     }
  1062.  
  1063.     @log = ();
  1064.  
  1065.     while( @transfer_list ){
  1066.         # get files
  1067.         $local_dir = shift( @transfer_list );
  1068.         $remote_dir = shift( @transfer_list );
  1069.         $get_patt = shift( @transfer_list );
  1070.  
  1071.         # Clear all details
  1072.         undef( @xfer_dest, @xfer_src, @xfer_attribs, @things_to_make );
  1073.  
  1074.         if( $use_files ){
  1075.             &create_assocs();
  1076.         }
  1077.  
  1078.         if( !&get_local_directory_details() ){
  1079.             &msg( "Cannot get local directory details ($local_dir)\n" );
  1080.             &disconnect();
  1081.             &msg( "\n" );
  1082.             return $exit_status;
  1083.         }
  1084.  
  1085.         # Create a get_patt from the contents of the local directory
  1086.         if( $update_local && $#get_top >= 0 ){
  1087.             $get_patt = '^' . join( '|^', @get_top );
  1088.             $get_patt =~ s/$squished//g;
  1089.             &msg( "get_patt = $get_patt\n" ) if $debug;
  1090.         }
  1091.     
  1092.         if( !&get_remote_directory_details() ){
  1093.             &msg( "Cannot get remote directory details ($remote_dir)\n" );
  1094.             &disconnect();
  1095.             &msg( "\n" );
  1096.             return $exit_status;
  1097.         }
  1098.     
  1099.         if( $get_file ){
  1100.             &compare_dirs(
  1101.                 *remote_sorted,
  1102.                  *remote_map, *remote_time,
  1103.                   *remote_size, *remote_type,
  1104.                 *local_sorted,
  1105.                  *local_map, *local_time,
  1106.                   *local_size, *local_type,
  1107.                    *local_keep, *local_keep_totals );
  1108.         } else {
  1109.             &compare_dirs(
  1110.                 *local_sorted,
  1111.                  *local_map, *local_time,
  1112.                   *local_size, *local_type,
  1113.                 *remote_sorted,
  1114.                  *remote_map, *remote_time,
  1115.                   *remote_size, *remote_type,
  1116.                    *remote_keep, *remote_keep_totals );
  1117.         }
  1118.  
  1119.         if( $timestamp ){
  1120.             &set_timestamps();
  1121.             next;
  1122.         }
  1123.  
  1124.         &make_dirs();
  1125.         &do_all_transfers();
  1126.  
  1127.         $exit_status = $exit_ok;    # Everything went ok.
  1128.  
  1129.         if( $get_file ){
  1130.             # I must have finished with the remote information
  1131.             # so clear it out.
  1132.             &clear_remote();
  1133.         }
  1134.         else {
  1135.             # clear out local info.
  1136.             &clear_local();
  1137.         }
  1138.         
  1139.         if( $save_deletes ){
  1140.             # If $save_dir is null, make $save_dir to be
  1141.             # subdirectory 'Old' under 
  1142.             # current path
  1143.             if( ( ! defined( $save_dir ) ) || ( $save_dir eq '' ) ){
  1144.                 $save_dir = "$cwd/Old";
  1145.             }
  1146.  
  1147.             # If $save_dir is not absolute, take it as
  1148.             # subdirectory of current path
  1149.             if( $save_dir !~ m,^/, ){
  1150.                            $save_dir = "$cwd/$save_dir";
  1151.                    }
  1152.         }
  1153.  
  1154.         if( $do_deletes || $save_deletes ){
  1155.             if( $get_file ){
  1156.                 &do_deletes(
  1157.                     *local_sorted,
  1158.                      *local_map,
  1159.                       *local_type, *local_keep,
  1160.                        *local_totals, *local_keep_totals );
  1161.             }
  1162.             else {
  1163.                 &do_deletes(
  1164.                     *remote_sorted,
  1165.                      *remote_map,
  1166.                       *remote_type, *remote_keep,
  1167.                        *remote_totals, *remote_keep_totals );
  1168.             }
  1169.         }
  1170.  
  1171.         &make_symlinks();
  1172.         undef( @things_to_make );
  1173.  
  1174.         # No more transfers if the connection has died.
  1175.         last if ! $connected;
  1176.     }
  1177.  
  1178.     &clear_local();
  1179.     &clear_remote();
  1180.     
  1181.     if( $use_files ){
  1182.         # Close and zap.
  1183.         &delete_assocs();
  1184.     }
  1185.  
  1186.     # Should I force a disconnect now?
  1187.     if( $connected && $disconnect ){
  1188.         &disconnect();
  1189.     }
  1190.  
  1191.     if( $dont_do || $timestamp ){
  1192.         # Don't generate logs/email
  1193.         &msg( "\n" );
  1194.         return $exit_status;
  1195.     }
  1196.  
  1197.     local( $now );
  1198.     chop( $now = `date` );
  1199.     if( $update_log ){
  1200.         if( ! open( logg, ">>$update_log" ) ){
  1201.             &msg( "Cannot append to $update_log\n\n" );
  1202.             return $exit_fail;
  1203.         }
  1204.         print logg "mirroring $package ($site:$remote_dir) completed successfully @ $now\n";
  1205.         print logg @log;
  1206.         close( logg );
  1207.     }
  1208.  
  1209.     if( $#log >= 0 && $mail_to =~ /./ ){
  1210.         local( $arg );
  1211.         eval "\$arg = \"$mail_subject\"";
  1212.         if( ! open( mail, "|$mail_prog $arg $mail_to" ) ){
  1213.             &msg( "Cannot run: $com\n\n" );
  1214.             return $exit_fail;
  1215.         }
  1216.         print mail "Mirrored $package ($site:$remote_dir) $comment @ $now\n";
  1217.         print mail @log;
  1218.         close( mail );
  1219.     }
  1220.     undef( @log );
  1221.  
  1222.     return $exit_status;
  1223. }
  1224.  
  1225.  
  1226. sub disconnect
  1227. {
  1228.     if( $connected ){
  1229.         &msg( "disconnecting from $connected\n" ) if $debug;
  1230.         if( ! $ftp'fatalerror ){
  1231.             &ftp'quit();
  1232.         }
  1233.     }
  1234.     $connected = '';
  1235. }
  1236.  
  1237. # Connect to the site
  1238. # Return 0 on a fail,
  1239. # 1 if a connection was successfully made,
  1240. # 2 if already connected to the site
  1241. sub connect
  1242. {
  1243.     local( $attempts ) = 1; # Retry ONCE! Be friendly.
  1244.     local( $res );
  1245.  
  1246.     if( $connected eq $site && $curr_remote_user eq $remote_user ){
  1247.         # Already connected to this site!
  1248.         return 2;
  1249.     }
  1250.  
  1251.     # Clear out any session active session
  1252.     &disconnect();
  1253.  
  1254.     if( $proxy ){
  1255.         $ftp'proxy = $proxy;
  1256.         $ftp'proxy_gateway = $proxy_gateway;
  1257.         $ftp'proxy_ftp_port = $proxy_ftp_port;
  1258.         $ftp'site = $site;
  1259.     }
  1260.     $res = &ftp'open( $site, $ftp_port, $retry_call, $attempts );
  1261.     if( $res == 1 ){
  1262.         # Connected
  1263.         $connected = $site;
  1264.     }
  1265.     return $res;
  1266. }    
  1267.  
  1268. # This just prods the remote ftpd to prevent time-outs
  1269. sub prod
  1270. {
  1271.     if( $debug > 2 ){
  1272.         &msg( " prodding remote ftpd\n" );
  1273.     }
  1274.     &ftp'pwd();
  1275. }
  1276.  
  1277. sub clear_local
  1278. {
  1279.     undef( @local_sorted );
  1280.     if( ! $use_files ){
  1281.         undef( %local_map );
  1282.     }
  1283.     undef( @local_time, @local_size,
  1284.         @local_type, @local_mode,
  1285.         @local_keep, @local_totals, @local_keep_totals );
  1286. }
  1287.  
  1288. sub clear_remote
  1289. {
  1290.     undef( @remote_sorted );
  1291.     if( ! $use_files ){
  1292.         undef( %remote_map );
  1293.     }
  1294.     undef( @remote_time, @remote_size,
  1295.         @remote_type, @remote_mode,
  1296.         @remote_keep, @remote_totals, @remote_keep_totals );
  1297. }
  1298.  
  1299. sub get_local_directory_details
  1300. {
  1301.     local( @dirs, $dir );
  1302.     local( $last_prodded ) = time; # when I last prodded the remote ftpd
  1303.  
  1304.     $next_local_mapi = $map_init;
  1305.     
  1306.     &clear_local();
  1307.     
  1308.     # Make sure the first elem is 0.
  1309.     push( @local_time, 0 );
  1310.     push( @local_size, 0 );
  1311.     push( @local_type, 0 );
  1312.     push( @local_mode, 0 );
  1313.  
  1314.     @get_top = ();
  1315.  
  1316.     &msg( "Scanning local directory $local_dir\n" ) if $debug;
  1317.     
  1318.     if( ! -d $local_dir ){
  1319.         if( $dont_do || $timestamp ){
  1320.             return 1;
  1321.         }
  1322.         &msg( "$local_dir no such directory - creating it\n" );
  1323.         if( &mkdirs( $local_dir ) ){
  1324.             push( @log, "Created dir $local_dir\n" );
  1325.             $exit_xfer_status |= $exit_xfers;
  1326.         }
  1327.         else {
  1328.             &msg( $log, "FAILED to create local dir $local_dir\n" );
  1329.         }
  1330.     }
  1331.     if( !chdir( $local_dir ) ){
  1332.         &msg( "Cannot change directory to $local_dir\n" );
  1333.         return 0;
  1334.     }
  1335.  
  1336.     if( $local_dir =~ m,^/, ){
  1337.         $cwd = $local_dir;
  1338.     }
  1339.     else {
  1340.         chop( $cwd = `pwd` );
  1341.     }
  1342.  
  1343.     # @dirs is the list of all directories to scan
  1344.     # As subdirs are found they are added to the end of the list
  1345.     # and as 
  1346.     @dirs = ( "." );
  1347.     # Most of these variables should be locals in blocks below but
  1348.     # that seems to tickle a perl bug and causes a lot of memory to
  1349.     # be wasted.
  1350.     local( $dir_level ) = 0;
  1351.     local( $i ) = 0;
  1352.     local( $path, $time, $size, $type, $mode, $name, $isdir, $value, $follow );
  1353.     local( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1354.               $atime,$mtime,$ctime,$blksize,$blocks );
  1355.     while( defined( $dir = shift( @dirs ) ) ){
  1356.  
  1357.         if( !opendir( dir, $dir ) ){
  1358.             &msg( "Cannot open local directory $dir, skipping it\n" );
  1359.             next;
  1360.         }
  1361.  
  1362.         while( defined( $name = readdir( dir ) ) ){
  1363.             $isdir = 0;
  1364.  
  1365.             # Prod the remote system from time to time
  1366.             # To prevent time outs.  Only look once every 50 files
  1367.             # to save on unnecessary systems calls.
  1368.             if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
  1369.                 $last_prodded = time;
  1370.                 &prod();
  1371.             }
  1372.             $i ++;
  1373.  
  1374.             next if $name eq '.' || $name eq '..' ||
  1375.                 ($local_ignore && $name =~ /$local_ignore/);
  1376.  
  1377.             $path = "$dir/$name";
  1378.             $path =~ s,(^|/)\./,,;
  1379.             $follow = ($follow_local_symlinks ne '' && $path =~ /$follow_local_symlinks/);
  1380.             if( !$follow && -l $path ){
  1381.                 $value = readlink( $path );
  1382.                 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1383.                       $atime,$mtime,$ctime,$blksize,$blocks ) =
  1384.                     lstat( _ );
  1385.                 $size = $ssize;
  1386.                 $time = $mtime;
  1387.                 $type = "l $value";
  1388.                 $mode = $fmode;
  1389.             }
  1390.             elsif( ($isdir = ($follow ? (-d $path) : (-d _))) ||
  1391.                      -f _ ){
  1392.                 ( $dev,$ino,$fmode,$nlink,$uid,$gid,$rdev,$ssize,
  1393.                       $atime,$mtime,$ctime,$blksize,$blocks ) =
  1394.                     stat( _ );
  1395.                 $size = $ssize;
  1396.                 $time = $mtime;
  1397.                 $mode = $fmode;
  1398.                 if( $isdir ){
  1399.                     push( @dirs, $path ) if $recursive;
  1400.                     $type = 'd';
  1401.                 }
  1402.                 else {
  1403.                     $type = 'f';
  1404.                 }
  1405.                 if( $dir_level == 0 && $update_local ){
  1406.                     push( @get_top, $path );
  1407.                 }
  1408.             }
  1409.             else {
  1410.                 &msg( "unknown file type $path, skipping\n" );
  1411.                 next;
  1412.             }
  1413.             if( $debug > 2){
  1414.                 printf "local: %s %s %s %s 0%o\n",
  1415.                     $path, $size, $time, $type, $mode;
  1416.             }
  1417.             if( $max_age && $time < $max_age ){
  1418.                 &msg( "   too old: $path\n" ) if $debug > 1;
  1419.                 next;
  1420.             }
  1421.  
  1422.             push( @local_sorted, $path );
  1423.             local( $mapi ) = $next_local_mapi++;
  1424.             $local_map{ $path } = $mapi;
  1425.             push( @local_time, $time );
  1426.             push( @local_size, $size );
  1427.             push( @local_type, $type );
  1428.             push( @local_mode, $mode );
  1429.             if( $type eq 'd' ){
  1430.                 $local_totals[ 0 ]++;
  1431.             }
  1432.             else {
  1433.                 $local_totals[ 1 ]++;
  1434.             }
  1435.         }
  1436.         closedir( dir );
  1437.         $dir_level++;
  1438.  
  1439.         if( ! $recursive ){
  1440.             last;
  1441.         }
  1442.     }
  1443.     return 1;
  1444. }
  1445.  
  1446. # Return true if the remote directory listing was brought back safely.
  1447. sub get_remote_directory_details
  1448. {
  1449.     local( $use_rls ) = 0;
  1450.     local( $type_changed ) = 0;
  1451.  
  1452.     &msg( "Scanning remote directory $remote_dir\n" ) if $debug;
  1453.     
  1454.     $next_remote_mapi = $map_init;
  1455.     &clear_remote();
  1456.  
  1457.     # Make sure the first elem is 0.
  1458.     push( @remote_time, 0 );
  1459.     push( @remote_size, 0 );
  1460.     push( @remote_type, 0 );
  1461.     push( @remote_mode, 0 );
  1462.  
  1463.     if( ! &ftp'cwd( $remote_dir ) ){
  1464.         if( $get_file ){
  1465.             # no files to get
  1466.             return 0;
  1467.         }
  1468.  
  1469.         &msg( "Failed to change to remote directory ($remote_dir) trying to create it\n" );
  1470.         &mkdirs( $remote_dir );
  1471.  
  1472.         if( ! &ftp'cwd( $remote_dir ) ){
  1473.             &msg( "Cannot change to remote directory ($remote_dir) because: $ftp'response\n" );
  1474.             return 0;
  1475.         }
  1476.     }
  1477.  
  1478.     local( $rls );
  1479.  
  1480.     if( $local_ls_lR_file ){
  1481.         &msg( " Using local file $local_ls_lR_file for remote dir listing\n" ) if $debug;
  1482.         if( ! open( dirtmp, $local_ls_lR_file ) ){
  1483.             &msg( "Cannot open $local_ls_lR_file\n" );
  1484.             return 0;
  1485.         }
  1486.         $rls = "main'dirtmp";
  1487.     }
  1488.     elsif( $ls_lR_file ){
  1489.         local( $dirtmp );
  1490.  
  1491.         $dirtmp = "/tmp/.dir$$";
  1492.         if( $ls_lR_file =~ /\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$/ ){
  1493.             $dirtmp .= ".$1";
  1494.         }
  1495.  
  1496.         &msg( " Getting directory listing from remote file $ls_lR_file\n" ) if $debug;
  1497.         if( ! &ftp'get( $ls_lR_file, $dirtmp, 0 ) ){
  1498.             &msg( "Cannot get dir listing file\n" );
  1499.             return 0;
  1500.         }
  1501.         local( $unsquish );
  1502.         if( $dirtmp =~ /\.$sys_compress_suffix$/ ){
  1503.             $unsquish = $sys_compress_prog;
  1504.         }
  1505.         elsif( $dirtmp =~ /\.($gzip_suffix|$old_gzip_suffix)$/ ){
  1506.             $unsquish = $gzip_prog;
  1507.         }
  1508.         if( defined( $unsquish ) ){
  1509.             local( $f );
  1510.              $dirtmp =~ s/'/\\'/g;
  1511.             $f = $dirtmp;
  1512.             $dirtmp =~ s/\.($sys_compress_suffix|$gzip_suffix|$old_gzip_suffix)$//;
  1513.             &sys( "$unsquish -d < '$f' > '$dirtmp'" );
  1514.             unlink( $f );
  1515.         }
  1516.  
  1517.         open( dirtmp, $dirtmp ) || die "Cannot open $dirtmp";
  1518.         $rls = "main'dirtmp";
  1519.     }
  1520.     else {
  1521.         $use_ls = 1;
  1522.         if( ! &ftp'type( 'A' ) ){
  1523.             &msg( "Cannot set type to ascii for dir listing, ignored\n" );
  1524.             $type_changed = 0;
  1525.         }
  1526.         else {
  1527.             $type_changed = 1;
  1528.         }
  1529.     }
  1530.     
  1531.     $lsparse'fstype = $remote_fs;
  1532.     $lsparse'name = "$site:$package";
  1533.     
  1534.     if( $use_ls ){
  1535.          if( !&ftp'dir_open( $recursive ? $flags_recursive : $flags_nonrecursive ) ){
  1536.             &msg( "Cannot get remote directory listing because: $ftp'response\n" );
  1537.             return 0;
  1538.         }
  1539.         
  1540.         $rls = "ftp'NS";
  1541.     }
  1542.         
  1543.     $rcwd = '';
  1544.     if( $vms ){
  1545.         # Strip this off all pathnames to make them
  1546.         # relative to the remote_dir
  1547.         $rcwd = $remote_dir;
  1548.     }
  1549.     if( !&lsparse'reset( $rcwd ) ){
  1550.         &msg( "$remote_fs: unknown fstype\n" );
  1551.         return 0;
  1552.     }
  1553.     if( $vms ){
  1554.         # Need to get in terms of the full pathname
  1555.         # so add it back in - see unix2vms at end of mirror
  1556.         $vms_dir = $remote_dir;
  1557.     }
  1558.     
  1559.     &parse_remote_details();
  1560.     
  1561.     if( $local_ls_lR_file ){
  1562.         close( dirtmp );
  1563.     }
  1564.     elsif( $ls_lR_file ){
  1565.         close( dirtmp );
  1566.         unlink( $dirtmp );
  1567.     }
  1568.     else {
  1569.         # Could optimise this out - but it makes sure that
  1570.         # the other end gets a command straight after a possibly
  1571.         # long dir listing.
  1572.         if( ! &ftp'type( $text_mode ? 'A' : 'I' ) ){
  1573.             local( $msg ) = "Cannot reset type after dir listing, ";
  1574.             if( $type_changed ){
  1575.                 # I changed it before - so I must be able to
  1576.                 # change back unless something is wrong
  1577.                 $msg .= "aborting\n";
  1578.                 &msg( $msg );
  1579.                 return 0;
  1580.             }
  1581.             else {
  1582.                 $msg .= "ignoring\n";
  1583.                 &msg( $msg );
  1584.             }
  1585.         }
  1586.     }
  1587.     
  1588.     # If the other end dropped part way thru make sure the
  1589.     # higher routines know!
  1590.     return ! $parse_timed_out;
  1591. }
  1592.  
  1593. sub parse_timeout
  1594. {
  1595.     $parse_timed_out = 1;
  1596.     die "timeout: parse_remote_details";
  1597. }
  1598.  
  1599. sub parse_remote_details
  1600. {
  1601.     $parse_timed_out = 0;
  1602.     
  1603.     if( ! $use_ls ){
  1604.         # No need to bother with the timers
  1605.         return &parse_remote_details_real();
  1606.     }
  1607.     
  1608.     # This may timeout
  1609.     $SIG{ 'ALRM' } = "main\'parse_timeout";
  1610.     
  1611.     eval '&parse_remote_details_real()';
  1612.     
  1613.     alarm( 0 );
  1614.  
  1615.     if( $@ =~ /^timeout/ ){
  1616.         &msg( "timed out parsing directory details\n" );
  1617.         return 0;
  1618.     }
  1619.     return 1;
  1620. }
  1621.  
  1622.  
  1623. sub parse_remote_details_real
  1624. {
  1625.     local( $path, $size, $time, $type, $mode, $rdir, $rcwd );
  1626.     local( @dir_list );
  1627.     local( $i ) = 0;
  1628.     
  1629.     if( $use_ls ){
  1630.         alarm( $parse_time );
  1631.     }
  1632.     
  1633.     # Need to loop in case $recurse_hard
  1634.     while( 1 ){
  1635.         while( !eof( $rls ) ){
  1636.             ( $path, $size, $time, $type, $mode ) =
  1637.                 &lsparse'line( $rls );
  1638.             last if !$path;
  1639.             if( $debug > 2 ){
  1640.                 printf "remote: %s %s %s %s 0%o\n",
  1641.                      $path, $size, $time, $type, $mode;
  1642.             }
  1643.             if( $use_ls ){
  1644.                 # I just got something so shouldn't timeout
  1645.                 alarm( $parse_time );
  1646.             }
  1647.             else {
  1648.                 # Prod the remote system from time to time
  1649.                 # To prevent time outs.  Only look once every
  1650.                 # 50 files
  1651.                 # to save on unnecessary systems calls.
  1652.                 if( ($i % 50 == 0) &&
  1653.                     time > ($last_prodded + $prod_interval) ){
  1654.                     $last_prodded = time;
  1655.                     &prod();
  1656.                 }
  1657.                 $i ++;
  1658.             }
  1659.             
  1660.             
  1661.             if( $type eq 'd' && $recurse_hard ){
  1662.                 push( @dir_list, $path );
  1663.             }
  1664.             
  1665.             if( $max_age && $time < $max_age ){
  1666.                 &msg( "   too old: $path\n" ) if $debug > 1;
  1667.                 next;
  1668.             }
  1669.             
  1670.             if( $exclude_patt && $path =~ /$exclude_patt/ ){
  1671.                 &msg( "   exclude: $path\n" ) if $debug > 1;
  1672.                 next;
  1673.             }
  1674.  
  1675.             # If vms and only keeping the latest version
  1676.             if( $vms && !$vms_keep_versions ){
  1677.                 # If we already have a file, pick the newer
  1678.                 # TODO: pick the greatest version number
  1679.                 local( $ri ) = $remote_map{ $path };
  1680.                 if( $ri &&
  1681.                     $time > $remote_time[ $ri ] ){
  1682.                     $remote_time[ $ri ] = $time;
  1683.                     $remote_size[ $ri ] = $size;
  1684.                     $remote_type[ $ri ] = $type;
  1685.                     $remote_mode[ $ri ] = $mode;
  1686.                     next;
  1687.                 }
  1688.             }
  1689.             
  1690.             push( @remote_sorted, $path );
  1691.             local( $mapi ) = $next_remote_mapi++;
  1692.             $remote_map{ $path } = $mapi;
  1693.             push( @remote_time, $time );
  1694.             push( @remote_size, $size );
  1695.             push( @remote_type, $type );
  1696.             push( @remote_mode, $mode );
  1697.             if( $type eq 'd' ){
  1698.                 $remote_totals[ 0 ]++;
  1699.             }
  1700.             else {
  1701.                 $remote_totals[ 1 ]++;
  1702.             }
  1703.         }
  1704.  
  1705.         if( $use_ls ){
  1706.             &ftp'dir_close();
  1707.             last;
  1708.         }
  1709.         
  1710.         if( $recurse_hard && $#dir_list >= 0 ){
  1711.             $rcwd = shift( @dir_list );
  1712.             $rdir = "$remote_dir/$rcwd";
  1713.             if( $debug > 2 ){
  1714.                 print "scanning: $remote_dir / $rcwd\n";
  1715.             }
  1716.             if( ! &ftp'cwd( $rdir ) ){
  1717.                 &msg( "Cannot change to remote directory" .
  1718.                  " ($rdir) because: $ftp'response\n" );
  1719.                 return 0;
  1720.             }
  1721.              if( !&ftp'dir_open( $recursive ? $flags_recursive : $flags_nonrecursive ) ){
  1722.                 &msg( "Cannot get remote directory" .
  1723.                       " listing because: $ftp'response\n" );
  1724.                 return 0;
  1725.             }
  1726.             &lsparse'reset( $rcwd );
  1727.             
  1728.             # round the loop again.
  1729.             next;
  1730.         }
  1731.         
  1732.         # All done - snap the loop
  1733.         last;
  1734.     }
  1735. }
  1736.  
  1737. sub compare_dirs
  1738. {
  1739.     local( *src_paths,
  1740.         *src_map, *src_time,
  1741.          *src_size, *src_type, 
  1742.            *dest_paths,
  1743.         *dest_map, *dest_time,
  1744.          *dest_size, *dest_type,
  1745.           *dest_keep, *dest_keep_totals ) = @_;
  1746.     local( $src_path, $dest_path, $dest_index, $i );
  1747.     local( $last_prodded ) = time; # when I last prodded the remote ftpd
  1748.  
  1749.     # Most of these variables should be locals in blocks below but
  1750.     # that seems to tickle a perl bug and causes a lot of memory to
  1751.     # be wasted.
  1752.     local( $desti, $srci, $compress, $srciZ, $srcigz, $split, $dest_path_real );
  1753.     local( $old_dest_path, $existing_path, $tmp, $tempi, $restart );
  1754.     
  1755.     &msg( "compare directories\n" ) if $debug;
  1756.  
  1757.     for( $i = 0; $i <= $#src_paths; $i++ ){
  1758.         $dest_path = $src_path = $src_paths[ $i ];
  1759.         
  1760.         $desti = $dest_map{ $dest_path };
  1761.         $srci = $i + 1;
  1762.  
  1763.         # Prod the remote system from time to time
  1764.         # To prevent time outs.  Only look once every 50 files
  1765.         # to save on unnecessary systems calls.
  1766.         if( ($i % 50 == 0) && time > ($last_prodded + $prod_interval) ){
  1767.             $last_prodded = time;
  1768.             &prod();
  1769.         }
  1770.  
  1771.         if( $debug > 2 ){
  1772.             &msg( "Compare src $src_path ($srci): $src_time[ $srci ]" );
  1773.             &msg( " $src_size[ $srci ] $src_type[ $srci ]\n" );
  1774.         }
  1775.  
  1776.         # I'm about to do a lot of matching on this
  1777.         study( $src_path );
  1778.  
  1779.         # Should I compress this file?
  1780.         #  Don't compress this file if trying to do a compress->gzip
  1781.         # conversion.
  1782.         $compress = 0;
  1783.         if( $src_type[ $srci ] eq 'f' &&
  1784.            $compress_patt && $src_path =~ /$compress_patt/ &&
  1785.            !($compress_excl && $src_path =~ /$compress_excl/i) &&
  1786.            !($compress_suffix eq $gzip_suffix &&
  1787.              $compress_conv_patt && $src_path =~ /$compress_conv_patt/)){
  1788.             if( $dest_path !~ /$squished/o ){
  1789.                 $srciZ = $src_map{ "$src_path.$sys_compress_suffix" };
  1790.                 $srcigz = $src_map{ "$src_path.$gzip_suffix" };
  1791.                 if( ($srciZ && $src_type[ $srciZ ] eq 'f') ||
  1792.                     ($srcigz && $src_type[ $srcigz ] eq 'f') ){
  1793.                     # There is a compressed version
  1794.                     # too!  Skip the uncompressed one
  1795.                     &msg( "   do not xfer, compressed version exists: $src_path\n" ) if $debug > 1;
  1796.                     next;
  1797.                 }
  1798.  
  1799.                 $compress = 1;
  1800.                 $dest_path .= '.' . $compress_suffix;
  1801.                 $desti = $dest_map{ $dest_path };
  1802.             }
  1803.         }
  1804.         # Squishing a file pointed to by a non-squished symlink/
  1805.         elsif( $src_type[ $srci ] =~ /^l (.*)/ &&
  1806.             $dest_path !~ /$squished/o ){
  1807.            # Am I going to squish the file this points to?
  1808.            local( $real ) = $1;
  1809.            local( $reali ) = $dest_map{ $real };
  1810.            if( $dest_type[ $reali ] ne 'd' &&
  1811.                $compress_patt && $real =~ /$compress_patt/ &&
  1812.                !($compress_excl && $real =~ /$compress_excl/i) &&
  1813.                !($compress_suffix eq $gzip_suffix &&
  1814.                  $compress_conv_patt && $real =~ /$compress_conv_patt/)){
  1815.                 # real is going to be (at least) squished so
  1816.                 # suffix the dest
  1817.                 $dest_path .= '.' . $compress_suffix;
  1818.                 $desti = $dest_map{ $dest_path };
  1819.                 $src_type[ $srci ] .= '.' . $compress_suffix;
  1820.                 &msg( "  symlink pointer is now $dest_path\n" ) if $debug > 1;
  1821.            }
  1822.         }
  1823.         
  1824.         # If this is a file that I decided not to compress but the
  1825.         # remote file is compressed and I want a gziped local version
  1826.         # then force compression.
  1827.         # This ignores any compress_excl flags.
  1828.         if( ! $compress &&
  1829.             $compress_suffix eq $gzip_suffix &&
  1830.             $src_path =~ /$compress_conv_patt/ ){
  1831.             $_ = $dest_path;
  1832.             eval $compress_conv_expr;
  1833.             $dest_path = $_;
  1834.             &msg( "   $src_path -> $dest_path\n" ) if $debug > 2;
  1835.             $desti = $dest_map{ $dest_path };
  1836.             $compress = 1;
  1837.         }
  1838.  
  1839.         # Am I converting the compression on the file this points to?
  1840.         if( $src_type[ $srci ] =~ /^l (.*)/ &&
  1841.               $compress_suffix eq $gzip_suffix ){
  1842.             local( $real ) = $1;
  1843.             if( $real =~ /$compress_conv_patt/ ){
  1844.                 $dest_path =~ s/\$sys_compress_suffix$/$gzip_suffix/;
  1845.                 $desti = $dest_map{ $dest_path };
  1846.                 $src_type[ $srci ] =~
  1847.                     s/$sys_compress_suffix$/$gzip_suffix/;
  1848.                 &msg( "  symlink pointer is now $dest_path (conv)\n")
  1849.                  if $debug > 1;
  1850.             }
  1851.         }
  1852.         
  1853.         # Should this file be split?
  1854.         $split = 0;
  1855.         $dest_path_real = undef;
  1856.         if( $split_max &&
  1857.            $src_type[ $srci ] eq 'f' &&
  1858.            $src_size[ $srci ] > $split_max &&
  1859.            $split_patt && $src_path =~ /$split_patt/ ){
  1860.             $split = 1;
  1861.             $dest_path_real = $dest_path;
  1862.             $dest_path .= "-split/part01";
  1863.             $desti = $dest_map{ $dest_path };
  1864.         }
  1865.  
  1866.         if( $debug > 2 ){
  1867.             &msg( "       dest $dest_path ($desti): $dest_time[ $desti ]" );
  1868.             &msg( " $dest_size[ $desti ] $dest_type[ $desti ]" );
  1869.             &msg( " (->$compress_suffix)" ) if $compress;
  1870.             &msg( " (split)" ) if $split;
  1871.             &msg( "\n" );
  1872.         }
  1873.         
  1874.         if( $name_mappings ){
  1875.             local( $old_dest_path ) = $dest_path;
  1876.             $_ = $dest_path;
  1877.             eval $name_mappings;
  1878.             if( $_ ne $old_dest_path ){
  1879.                 $dest_path = $_;
  1880.                 $desti = $dest_map{ $dest_path };
  1881.                 &msg( "   Mapped name is $dest_path\n" ) if $debug > 2;
  1882.             }
  1883.         }
  1884.  
  1885.         if( $external_mapping ){
  1886.             $old_dest_path = $dest_path;
  1887.             local( $tmp ) = &extmap'map( $dest_path );
  1888.             if( $tmp ne $old_dest_path ){
  1889.                 $dest_path = $tmp;
  1890.                 $desti = $dest_map{ $dest_path };
  1891.                 &msg( "   Mapped name is $dest_path\n" ) if $debug > 2;
  1892.             }
  1893.         }
  1894.  
  1895.         if( $get_patt && $src_path !~ /$get_patt/ ){
  1896.             &msg( "   do not xfer: $src_path\n" ) if $debug > 1;
  1897.             next;
  1898.         }
  1899.  
  1900.         # Just create any needed directories (the timestamps
  1901.         # should be ignored)
  1902.         if( $src_type[ $srci ] eq 'd' ){
  1903.             if( $dest_type[ $desti ] ne 'd' ){
  1904.                 push( @things_to_make, "d $dest_path" );
  1905.                 &msg( "   need to mkdir $dest_path\n" ) if $debug > 1;
  1906.             }
  1907.             # keep the directory once made
  1908.             # (Also if local is really a symlink elsewhere
  1909.             #  it will be kept.)
  1910.             $dest_keep[ $desti ] = 1;
  1911.             $dest_keep_totals[ 0 ]++;
  1912.             &msg( "   keep $dest_path\n" ) if $debug > 2;
  1913.             next;
  1914.         }
  1915.  
  1916.         # Well that just leaves files and symlinks.
  1917.         # Do various checks on them.
  1918.  
  1919.         if( $desti && ! $dest_keep[ $desti ] ){
  1920.             $dest_keep[ $desti ] = 1;
  1921.             $dest_keep_totals[ 1 ]++;
  1922.             &msg( "   keep $dest_path\n" ) if $debug > 2;
  1923.             if( $split ){
  1924.                 # Mark all the split parts as kept
  1925.                 local( $dp ) = $dest_path;
  1926.                 local( $di );
  1927.                 while( 1 ){
  1928.                     $dp++;
  1929.                     if( !($di = $dest_map{ $dp }) ){
  1930.                         last;
  1931.                     }
  1932.                     $dest_keep[ $di ] = 1;
  1933.                     $dest_keep_totals[ 1 ]++;
  1934.                     &msg( "   keep $dp\n" ) if $debug > 2;
  1935.                 }
  1936.                 # And the README
  1937.                 $dp =~ s/part.*$/README/;
  1938.                 $di = $dest_map{ $dp };
  1939.                 if( $di ){
  1940.                     $dest_keep[ $di ] = 1;
  1941.                     $dest_keep_totals[ 1 ]++;
  1942.                     &msg( "   keep $dp\n" ) if $debug > 2;
  1943.                 }
  1944.                 # And the directory
  1945.                 $dp =~ s,/README,,;
  1946.                 $di = $dest_map{ $dp };
  1947.                 if( $di ){
  1948.                     $dest_keep[ $di ] = 1;
  1949.                     $dest_keep_totals[ 0 ]++;
  1950.                     &msg( "   keep $dp\n" ) if $debug > 2;
  1951.                 }
  1952.             }
  1953.         }
  1954.         
  1955.         local( $update ) = 0;
  1956.  
  1957.         if( $force || ! $dest_type[ $desti ] || $timestamp ){
  1958.             # Either I'm forcing xfers or the file doesn't exist
  1959.             # either way I should update
  1960.             $update = 1;
  1961.         }
  1962.         else {
  1963.             # Maybe the src is newer?
  1964.             if( $get_newer &&
  1965.                &compare_times( $src_time[ $srci ], $dest_time[ $desti ] ) ){
  1966.                 &msg( "   src is newer, xfer it\n" ) if $debug > 2;
  1967.                 $update = 1;
  1968.             }
  1969.             # or maybe its size has changed?
  1970.             # don't bother if file was compressed or split as the
  1971.             # size will have changed anyway
  1972.             if( !$compress && !$split &&
  1973.                $get_size_change &&
  1974.                ($src_type[ $srci ] eq 'f') &&
  1975.                ($src_size[ $srci ] != $dest_size[ $desti ]) ){
  1976.                 &msg( "   src is different size, xfer it\n" ) if $debug > 2;
  1977.                 $update = 1;
  1978.             }
  1979.             # Maybe it has changed type!
  1980.             if( $src_type[ $srci ] ne $dest_type[ $desti ] ){
  1981.                 $update = 1;
  1982.             }
  1983.         }
  1984.  
  1985.         if( ! $update ){
  1986.             next;
  1987.         }
  1988.  
  1989.         if( $src_type[ $srci ] =~ /^l (.*)/ ){
  1990.             # If the symlink hasn't changed then may as well 
  1991.             # leave it alone
  1992.             if( $src_type[ $srci ] eq $dest_type[ $desti ] ){
  1993.                 next;
  1994.             }
  1995.             # DONT FORGET TO NAME MAP!!!!
  1996.             $existing_path = $1;
  1997.  
  1998.             if( $compress_suffix eq $gzip_suffix &&
  1999.                 $existing_path =~ /$compress_conv_patt/ ){
  2000.                 $_ = $existing_path;
  2001.                 eval $compress_conv_expr;
  2002.                 $existing_path = $_;
  2003.             }
  2004.  
  2005.             push( @things_to_make, "l $dest_path -> $existing_path" );
  2006.             &msg( "   need to symlink $dest_path -> $existing_path\n" ) if $debug > 2;
  2007.             next;
  2008.         }
  2009.  
  2010.         # Now that the tests are complete use the real dest.
  2011.         if( defined( $dest_path_real ) ){
  2012.             $dest_path = $dest_path_real;
  2013.             $desti = $dest_map{ $dest_path };
  2014.         }
  2015.  
  2016.         &msg( "$XFER file $src_path as $dest_path".
  2017.             ($compress ? " (->$compress_suffix)" : "") .
  2018.             ($split ? " (split)" : "") . "\n" ) if $debug > 1;
  2019.         push( @xfer_dest, $dest_path );
  2020.         push( @xfer_src, $src_path );
  2021.  
  2022.         # If xfers can be restarted AND
  2023.         # a temporary file exists from a previous attempt at a
  2024.         # transfer  AND
  2025.         # the timestamps of the exising temp file and the original
  2026.         # src file match then flag a restart.
  2027.         $tmp = &filename_to_tempname( '', $dest_path );
  2028.         $tmpi = $dest_map{ $tmp };
  2029.         $restart = '';
  2030.         if( $get_file &&
  2031.            $can_restart &&
  2032.            -f $tmp &&
  2033.            ($dest_time[ $tmpi ] eq $src_time[ $srci ]) ){
  2034.             # Then this is an xfer of the same file
  2035.             # so just restart where I left off
  2036.             $restart = 'r';
  2037.         }
  2038.         # x for xfer, c for compress, s for split
  2039.         push( @xfer_attribs,
  2040.              "x$restart" .
  2041.              ($compress ? "c" : "") .
  2042.              ($split ? "s" : "") );
  2043.     }
  2044. }
  2045.  
  2046. sub set_timestamps
  2047. {
  2048.     local( $src_path );
  2049.     
  2050.     &msg( "setting timestamps\n" );
  2051.     if( ! $get_file ){
  2052.         &msg( "Cannot set remote timestamps\n" );
  2053.         return;
  2054.     }
  2055.  
  2056.     local( $dest_path, $dest_loc_mapi, $src_rem_mapi,  $rtime );
  2057.     
  2058.     foreach $src_path ( @xfer_src ){
  2059.         $dest_path = shift( @xfer_dest );
  2060.         $dest_loc_mapi = $local_map{ $dest_path };
  2061.         $src_rem_mapi = $remote_map{ $src_path };
  2062.  
  2063.         $rtime = $remote_time[ $src_rem_mapi ];
  2064.         if( $dest_loc_mapi && $local_time[ $dest_loc_mapi ] ne $rtime ){
  2065.             &set_timestamp( $dest_path, $rtime );
  2066.         }
  2067.     }
  2068. }
  2069.  
  2070. sub set_timestamp
  2071. {
  2072.     local( $path, $time ) =  @_;
  2073.     
  2074.     if( $dont_do ){
  2075.         &msg( "Should set time of $path to $time\n" );
  2076.         return;
  2077.     }
  2078.  
  2079.     if( $timestamp || $debug > 2 ){
  2080.         &msg( "Setting time of $path to $time\n" );
  2081.     }
  2082.     utime( $time, $time, $path );
  2083. }
  2084.  
  2085. sub make_dirs
  2086. {
  2087.     local( $thing );
  2088.  
  2089.     return if $dont_do;
  2090.  
  2091.     foreach $thing ( @things_to_make ){
  2092.         if( $thing !~ /^d (.*)/ ){
  2093.             next;
  2094.         }
  2095.         &mkdirs( $1 );
  2096.     }
  2097. }
  2098.  
  2099. sub make_symlinks
  2100. {
  2101.     local( $thing );
  2102.  
  2103.     return if $dont_do;
  2104.  
  2105.     thing:
  2106.     foreach $thing ( @things_to_make ){
  2107.         if( $thing !~ /^l (.*) -> (.*)/ ){
  2108.             next;
  2109.         }
  2110.         local( $dest, $existing ) = ($1, $2);
  2111.         local( $dirpart ) = &dirpart( $dest );
  2112.         if( -e "$dirpart/$existing" ){
  2113.             # symlink to existing file.
  2114.             &mksymlink( $dest, $existing );
  2115.             next;
  2116.         }
  2117.         # The existing file doesn't actually exist!
  2118.         # Has it been compressed, gzipped, split? or worse
  2119.         # compressed/gzipped AND split.  (OK so it could
  2120.         # be another problem, bad symlink on remote host, file
  2121.         # that hasn't been xfer'd yet... but this is as good as
  2122.         # it gets.)
  2123.         local( $p );
  2124.         foreach $p (
  2125.             "\%s.$sys_compress_suffix",
  2126.             "\%s.$gzip_suffix",
  2127.             "\%s/README",
  2128.             "\%s-split/README",
  2129.             "\%s-split.$sys_compress_suffix/README",
  2130.             "\%s-split.$gzip_suffix/README" ){
  2131.             local( $f ) = sprintf( $p, $existing );
  2132.             if( -e $f ){
  2133.                 &msg( "using $p\n" ) if $debug > 2;
  2134.                 &mksymlink( $dest, $f );
  2135.                 next thing;
  2136.             }
  2137.         }
  2138.         if( $make_bad_symlinks ){
  2139.             &msg( "symlink to non-existant file: $dest -> $existing\n" );
  2140.             &mksymlink( $dest, $existing );
  2141.         }
  2142.         else {
  2143.             &msg( "Not symlinking $dest -> $existing\n" );
  2144.         }
  2145.     }
  2146. }
  2147.  
  2148. sub do_all_transfers
  2149. {
  2150.     local( $src_path );
  2151.     
  2152.     if( $#xfer_src < 0 ){
  2153.         &msg( "No files to transfer\n" );
  2154.         return;
  2155.     }
  2156.  
  2157.     foreach $src_path ( @xfer_src ){
  2158.         local( $dest_path, $attribs );
  2159.         local( $srci );
  2160.         
  2161.         if( $get_file ){
  2162.             $srci = $remote_map{ $src_path };
  2163.         }
  2164.         else {
  2165.             $srci = $local_map{ $src_path };
  2166.         }
  2167.  
  2168.         $dest_path = shift( @xfer_dest );
  2169.         $attribs = shift( @xfer_attribs );
  2170.         
  2171.         if( $dont_do ){
  2172.             # Skip trying to get the file.
  2173.             next;
  2174.         }
  2175.  
  2176.         &msg( "Need to $XFER file $src_path as $dest_path ($attribs)\n" ) if $debug > 1;
  2177.  
  2178. #        &msg( "transferring $src_path " );
  2179.         local( $newpath ) =
  2180.             &transfer_file( $src_path, $dest_path,
  2181.                        $attribs, $remote_time[ $srci ] );
  2182.         if( $get_file && $newpath eq '' ){
  2183.             &msg( "Failed to $XFER file $ftp'response\n" );
  2184.             if( $ftp'response =~ /timeout|timed out/i ){
  2185.                 $timeouts++;
  2186.             }
  2187.             if( $ftp'fatalerror || $timeouts > $max_timeouts ){
  2188.                 &msg( "Fatal error talking to site, skipping rest of transfers\n" );
  2189.                 &disconnect();
  2190.                 return;
  2191.             }
  2192.             next;
  2193.         }
  2194.  
  2195.         # File will now have been split up.
  2196.         if( $attribs =~ /s/ ){
  2197. #            &msg( "\n" );
  2198.             next;
  2199.         }
  2200.  
  2201.         if( $newpath ne $src_path ){
  2202. #            &msg( "into $newpath" );
  2203.         }
  2204. #        &msg( "\n" );
  2205.  
  2206.         &set_attribs( $newpath, 'f' );
  2207.  
  2208.         # we can only force time for local files
  2209.         if( $force_times && $get_file ){
  2210.             &set_timestamp( $newpath, $remote_time[ $srci ] );
  2211.         }
  2212.     }
  2213. }
  2214.  
  2215.  
  2216. sub transfer_file
  2217. {
  2218.     local( $src_path, $dest_path, $attribs, $timestamp ) = @_;
  2219.     local( $dir, $file, $temp, $compress, $split, $restart, $mesg, $got_mesg );
  2220.     
  2221.     # Make sure the required directory exists
  2222.     $dir = "";
  2223.     if( $dest_path =~ /^(.+\/)([^\/]+)$/ ){
  2224.         ($dir, $file) = ($1, $2);
  2225.         if( $dest_type[ $dir ] ne 'd' && &mkdirs( $dir ) ){
  2226.             &msg( $log, "Created dir $dir\n" );
  2227.         }
  2228.     }
  2229.     else {
  2230.         $file = $dest_path;
  2231.     }
  2232.     
  2233.     $temp = &filename_to_tempname( $dir, $file );
  2234.     
  2235.     # Interpret the attrib characters
  2236.     if( $attribs !~ /x/ ){
  2237.         # Not an xfer!
  2238.         return '';
  2239.     }
  2240.     if( $attribs =~ /c/ ){
  2241.         $compress = 1;
  2242.         $mesg = " and compress";
  2243.     }
  2244.     if( $attribs =~ /s/ ){
  2245.         $split = 1;
  2246.         $mesg = " and split";
  2247.     }
  2248.     if( $attribs =~ /r/ ){
  2249.         $restart = 1;
  2250.     }
  2251.     
  2252.     if( $vms ){
  2253.         &ftp'type( ($src_file =~ /$vms_xfer_text/i) ? 'A' : 'I' );
  2254.     }
  2255.     
  2256.     if( ! $get_file ){
  2257.         # put the file remotely
  2258.         local( $src_file ) = $src_path;
  2259.         local( $comptemp ) = '';
  2260.  
  2261.         if( $compress ){
  2262.             # Prevent the shell from expanding characters
  2263.             # No easy way to tell wether this was compressed or not
  2264.             # for now just presume that it is.
  2265.             local( $f ) = $src_file;
  2266.             $f =~ s/'/\\'/g;
  2267.             $comptemp = "$big_temp/.out$$";
  2268.             &sys( "$compress_prog < '$f' > $comptemp" );
  2269.             $src_file = $comptemp;
  2270.         }
  2271.         
  2272.         if( ! &ftp'put( $src_file, $temp, $restart ) ){
  2273.             &msg( $log, "Failed to put $src_file: $ftp'response\n" );
  2274.             unlink( $comptemp ) if $comptemp;
  2275.             return '';
  2276.         }
  2277.     
  2278.         unlink( $comptemp ) if $comptemp;
  2279.         if( ! &ftp'rename( $temp, $dest_path ) ){
  2280.             &msg( $log, "Failed to remote rename $temp to $dest_path: $ftp'response\n" );
  2281.             return '';
  2282.         }
  2283.  
  2284.         # Some transfers done
  2285.         $exit_xfer_status |= $exit_xfers;
  2286.         
  2287.         return $dest_path;
  2288.     }
  2289.  
  2290.     # Get a file
  2291.     if( ! &ftp'get( $src_path, $temp, $restart ) ){
  2292. #        &msg( $log, "Failed to get $src_path: $ftp'response\n" );
  2293.  
  2294.         # Time stamp the temp file to allow for a restart
  2295.         if( -f $temp ){
  2296.             utime( $timestamp, $timestamp, $temp );
  2297.             # Make sure this file is kept
  2298.             local( $ti ) = $local_map{ $temp };
  2299.             $local_keep[ $ti ] = 1;
  2300.             $local_keep_totals[ 0 ]++;
  2301.         }
  2302.  
  2303.         return '';
  2304.     }
  2305.     
  2306.     # Some transfers done
  2307.     $exit_xfer_status += $exit_xfers;
  2308.  
  2309.     # delete source file after successfull transter
  2310.     if( $delete_source ){
  2311.         if( &ftp'delete( $path ) ){
  2312.             &msg( $log, "Deleted remote $lpath\n");
  2313.         }
  2314.         else {
  2315.             &msg( $log, "Failed to delete remote $lpath\n");
  2316.         }
  2317.     }
  2318.  
  2319.     if( $compress ){
  2320.         # Prevent the shell from expanding characters
  2321.         local( $f ) = $temp;
  2322.         local( $comp );
  2323.         $f =~ s/'/\\'/g;
  2324.         $temp = "$f.$compress_suffix";
  2325.         # Am I doing compress to gzip conversion?
  2326.          if( $src_path =~ /$compress_conv_patt/ &&
  2327.             $compress_suffix eq $gzip_suffix ){
  2328.             $comp = "$sys_compress_prog -d < '$f' | $gzip_prog > '$temp'";
  2329.         }
  2330.         else {
  2331.              $comp = "$compress_prog < '$f' > '$temp'";
  2332.         }
  2333.         &sys( $comp );
  2334.         unlink( $f );
  2335.     }
  2336.  
  2337.     # Ok - chop it up into bits!
  2338.     if( $split ){
  2339.         local( $time ) = 0;
  2340.         if( $force_times ){
  2341.             $time = $remote_time[ $remote_map{ $src_path } ];
  2342.         }
  2343.         &bsplit( $temp, $dest_path, $time );
  2344.         unlink $temp;
  2345.         $got_mesg .= " and split";
  2346.     }
  2347.     else {
  2348.         rename( $temp, $dest_path );
  2349.     }
  2350.  
  2351.     local( $filesize ) = &filesize( $dest_path );
  2352.     local( $as ) = '';
  2353.     if( $src_path ne $dest_path ){
  2354.         $as = " as $dest_path";
  2355.     }
  2356.     &msg( $log, "Got $src_path$as$got_mesg $filesize\n" );
  2357.     # Make sure to keep what you just got!  It may/may no have
  2358.     # been compressed or gzipped..
  2359.     local( $locali ) = $local_map{ $dest_path };
  2360.     $local_keep[ $locali ] = 1;
  2361.  
  2362.     &log_upload( $src_path, $dest_path, $got_mesg, $filesize );
  2363.  
  2364.     return( $dest_path );
  2365. }
  2366.  
  2367. sub filename_to_tempname
  2368. {
  2369.     local( $dir, $file ) = @_;
  2370.  
  2371.     # dir 
  2372.     return "$dir.in.$file.";
  2373. }
  2374.  
  2375.  
  2376. # Open, write, close - to try and ensure that the log will allways be filled
  2377. # in.
  2378. sub log_upload
  2379. {
  2380.     local( $src_path, $dest_path, $got_mesg, $size ) = @_;
  2381.  
  2382.     if( ! $upload_log ){
  2383.         return;
  2384.     }
  2385.  
  2386.     if( ! open( ulog, ">>$upload_log" ) ){
  2387.         print STDERR "Cannot write to $upload_log\n";
  2388.         return;
  2389.     }
  2390.  
  2391.     print ulog "$site:$remote_dir/$src_path -> $local_dir/$dest_path $size ";
  2392.     if( $got_mesg ){
  2393.         print ulog "($got_mesg)";
  2394.     }
  2395.     print ulog "\n";
  2396.     close( ulog );
  2397. }
  2398.  
  2399. sub do_deletes
  2400. {
  2401.     local( *src_paths,
  2402.         *src_map,
  2403.          *src_type, *src_keep,
  2404.           *src_totals, *src_keep_totals ) = @_;
  2405.     
  2406.     if( ! ($do_deletes || $save_deletes) ){
  2407.         return;
  2408.     }
  2409.     
  2410.     local( $src_path, $i );
  2411.     local( $orig_do_deletes ) = $do_deletes;
  2412.  
  2413.     local( $del_patt ) = $delete_patt;
  2414.     if( $delete_get_patt ){
  2415.         $del_patt = $get_patt;
  2416.     }
  2417.     
  2418.     if( !$save_deletes && ($src_totals[ 1 ] - $src_keep_totals[ 1 ]) > $max_delete_files ){
  2419.         &msg( "Too many files to delete, not actually deleting\n" );
  2420.         $do_deletes = 0;
  2421.     }
  2422.  
  2423.     if( !$save_deletes && ($src_totals[ 0 ] - $src_keep_totals[ 0 ]) > $max_delete_dirs ){
  2424.         &msg( "Too many directories to delete, not actually deleting\n" );
  2425.         $do_deletes = 0;
  2426.     }
  2427.  
  2428.     # Scan the list backwards so subdirectories are dealt with first
  2429.     for( $i = $#src_paths; $i >= 0; $i-- ){
  2430.         $src_path = $src_paths[ $i ];
  2431.         $srci = $i + 1;
  2432.     
  2433.         if( $src_keep[ $srci ] ){
  2434.             # Keep this for sure;
  2435.             &msg( "Keeping: $src_path\n" ) if $debug > 3;
  2436.             next;
  2437.         }
  2438.  
  2439.         if( $src_path !~ /$del_patt/ ){
  2440.             &msg( "   not in del_patt: $src_path\n" ) if $debug > 1;
  2441.             next;
  2442.         }
  2443.  
  2444.         if( $delete_excl && $src_path =~ /$delete_excl/ ){
  2445.             &msg( "   do not delete: $src_path\n" ) if $debug > 1;
  2446.             next;
  2447.         }
  2448.  
  2449.         if( $save_deletes ){
  2450.             $save_dir =~ m,$cwd/(.*),;
  2451.             local( $save_dir_tail ) = $1;
  2452.             if( $save_dir_tail && $src_path =~ m,$save_dir_tail/*, ){
  2453.                 next;
  2454.             }
  2455.         }
  2456.  
  2457.         if( $save_deletes ){
  2458.             &save_delete( $src_path, $src_type[ $srci ] );
  2459.         }
  2460.         else {
  2461.             &do_delete( $src_path, $src_type[ $srci ] );
  2462.         }
  2463.     }
  2464.     
  2465.     $do_deletes = $orig_do_deletes;
  2466. }
  2467.         
  2468. # Move aside the given file.  Kind is 'd' for dirs and 'f' for files.
  2469. sub save_delete
  2470. {
  2471.     local( $save, $kind ) = @_;
  2472.  
  2473.     local( $real_save_dir, $save_dest );
  2474.     eval "\$real_save_dir = \"$save_dir\"";
  2475.  
  2476.  
  2477.     if( ! $get_file ){
  2478.         &msg( "NEED TO implement remote save_deletes\n" );
  2479.         return;
  2480.     }
  2481.     
  2482.     $save_dest = "$real_save_dir/$save";
  2483.  
  2484.     if( $dont_do ){
  2485.         &msg( "save_delete $save to $save_dest\n" );
  2486.         return;
  2487.     }
  2488.  
  2489.     if( $kind eq 'd' ){
  2490.         $save_dest =~ s,/+$,,;
  2491.         
  2492.         # Make sure it exists
  2493.         &save_mkdir( $save_dest );
  2494.             
  2495.         # Zap the original
  2496.         if( rmdir( $save ) == 1 ){
  2497.             &msg( $log, "Removed directory $save\n" );
  2498.         }
  2499.         else {
  2500.             &msg( $log, "UNABLE TO REMOVE DIRECTORY $save\n" );
  2501.         }
  2502.         return;
  2503.     }
  2504.  
  2505.     # Save a file
  2506.  
  2507.     # Make the directories under $save_dir
  2508.     local( $dirname );
  2509.     $dirname = $save_dest;
  2510.     $dirname =~ s/\/[^\/]+$//;
  2511.     # Make sure the directory exists to mv the file into.
  2512.     &save_mkdir( $dirname );
  2513.         
  2514.     if( rename( $save, $save_dest ) == 1 ){
  2515.         &msg( $log, "Moved $save to $save_dest\n" );
  2516.     }
  2517.     else {
  2518.         system "$mv_prog $save $save_dest";
  2519.         if( ( $? >> 8 ) == 0 ){
  2520.             &msg( $log, "Moved $save to $save_dest\n" );
  2521.         }
  2522.         else {
  2523.             &msg( $log, "UNABLE TO MOVE $save TO $save_dest, aborting saves\n" );
  2524.             $save_deletes = 0;
  2525.         }
  2526.     }
  2527. }
  2528.  
  2529. sub save_mkdir
  2530. {
  2531.     local( $dir ) = @_;
  2532.     
  2533.     if( ! -d $dir ){
  2534.         if( &mkdirs( $dir ) ){
  2535.             &msg( $log, "Created save directory $dir\n" );
  2536.         }
  2537.         else {
  2538.             &msg( $log, "UNABLE TO CREATE $dir, aborting saves\n" );
  2539.             $save_deletes = 0;
  2540.         }
  2541.     }
  2542. }
  2543.  
  2544. # Delete the given file.  Kind is 'd' for dirs and 'f' for files.
  2545. sub do_delete
  2546. {
  2547.     local( $del, $kind ) = @_;
  2548.     
  2549.     if( $dont_do ){
  2550.         &msg( "delete $del\n" );
  2551.         return;
  2552.     }
  2553.  
  2554.     if( $kind eq 'd' ){
  2555.         $del =~ s,/+$,,;
  2556.         if( $do_deletes ){
  2557.             if( $get_file ){
  2558.                 &msg( $log, "rmdir $cwd/$del\n" );
  2559.                 rmdir( "$cwd/$del" );
  2560.             }
  2561.             else {
  2562.                 &msg( $log, "Cannot delete remote directories\n" );
  2563.             }
  2564.         }
  2565.         else {
  2566.             if( $get_file ){
  2567.                 &msg( $log, "NEED TO rmdir $cwd/$del\n" );
  2568.             }
  2569.             else {
  2570.                 &msg( $log, "NEED TO ftp'deldir $del\n" );
  2571.             }
  2572.         }
  2573.         return;
  2574.     }    
  2575.  
  2576.     # Deleting a file.
  2577.     if( $do_deletes ){
  2578.         if( $get_file ){
  2579.             &msg( $log, "unlink $cwd/$del\n" );
  2580.             unlink( "$cwd/$del" );
  2581.         }
  2582.         else {
  2583.             &msg( $log, "delete $cwd/$del\n" );
  2584.             &ftp'delete( "$cwd/$del" );
  2585.         }
  2586.     }
  2587.     else {
  2588.         if( $get_file ){
  2589.             &msg( $log, "NEED TO unlink $cwd/$del\n" );
  2590.         }
  2591.         else {
  2592.             &msg( $log, "NEED TO ftp'delete $del\n" );
  2593.         }
  2594.     }
  2595. }
  2596.  
  2597. sub filesize
  2598. {
  2599.     local( $fname ) = @_;
  2600.  
  2601.     if( ! -f $fname ){
  2602.         return -1;
  2603.     }
  2604.  
  2605.     return (stat( _ ))[ 7 ];
  2606.     
  2607. }
  2608.  
  2609. # Is the value
  2610. sub istrue
  2611. {
  2612.     local( $val ) = @_;
  2613.     
  2614.     return $val eq '1' || $val eq 'yes' || $val eq 'ok' ||
  2615.            $val eq 'true';
  2616. }
  2617.  
  2618. sub mksymlink
  2619. {
  2620.     local( $dest_path, $existing_path ) = @_;
  2621.  
  2622.     if( ! $get_file ){
  2623.         &msg( "Cannot create symlinks on remote systems ($dest_path -> $existing_path)\n" );
  2624.         return;
  2625.     }
  2626.     
  2627.     # make the symlink locally
  2628.  
  2629.     # Zap any exiting file/symlink of that name
  2630.     if( -e $dest_path || -l $dest_path ){
  2631.         unlink( $dest_path );
  2632.         &msg( "unlink( $dest_path ) before symlink\n" ) if $debug;
  2633.     }
  2634.  
  2635.     if( (eval 'symlink("","")', $@ eq '') ){
  2636.         local( $status ) = '';
  2637.         if( ! symlink( $existing_path, $dest_path ) ){
  2638.             $status = "Failed to ";
  2639.         }
  2640.         &msg( $log, $status . "symlink $existing_path to $dest_path\n" );
  2641.     }
  2642.     else {
  2643.         &msg( $log, "Tried to create symlink - but not supported locally\n" );
  2644.     }
  2645. }
  2646.  
  2647.  
  2648. # Make a full directory heirarchy
  2649. # returns true if the directory doesn't exist
  2650. sub mkdirs
  2651. {
  2652.     local( $dir ) = @_;
  2653.     local( @dir, $d, $path );
  2654.  
  2655.     # Very often the directory does exist - so return now
  2656.     return 0 if &dir_exists( $dir );
  2657.     
  2658.     # Make sure that the target directory exists
  2659.     @dirs = split( '/', $dir );
  2660.     
  2661.     # the root directory always exists
  2662.     $path = '';
  2663.     if( $dirs[ 0 ] eq '' ){ 
  2664.         shift( @dirs ); 
  2665.         $path = '/';
  2666.     }
  2667.  
  2668.     foreach $d ( @dirs ){
  2669.         $path = $path . $d;
  2670.         if( ! &dir_exists( $path ) ){
  2671.             &msg( "mkdir $path\n" ) if $debug > 2;
  2672.             if( ! &make_dir( $path, 0755 ) ){
  2673.                 &msg( "make_dir($path,0755) failed with $err\n" );
  2674.                 return 0;
  2675.             }
  2676.             &set_attribs( $path, 'd' );
  2677.         }
  2678.         $path .= "/";
  2679.     }
  2680.     return 1;
  2681. }
  2682.  
  2683. # return 0 on error, 1 on success
  2684. sub make_dir
  2685. {
  2686.     local( $dir, $mode ) = @_;
  2687.     local( $val );
  2688.  
  2689.     if( $get_file ){
  2690.         # make a local directory
  2691.         if( -e $dir || -l $dir ){
  2692.             unlink( $dir );
  2693.         }
  2694.         $val = mkdir( $dir, $mode );
  2695.         $err = "$!";
  2696.     }
  2697.     else {
  2698.         # make a remote directory
  2699.         $val = &ftp'mkdir( $dir );
  2700.  
  2701.         # The mkdir might have failed due to bad mode
  2702.         # So try to chmod it anyway
  2703.         if( $remote_has_chmod ){
  2704.             $val = &ftp'chmod( $dir, $mode );
  2705.         }
  2706.     }
  2707.  
  2708.     return $val;
  2709. }
  2710.  
  2711. # return 1 if $dir exists, 0 if not
  2712. sub dir_exists
  2713. {
  2714.     local( $dir ) = @_;
  2715.     local( $val );
  2716.  
  2717.     if( $get_file ){
  2718.         # check if local directory exists
  2719.         $val = (-d $dir);
  2720.     }
  2721.     else {
  2722.         # check if remote directory exists
  2723.         local($old_dir) = &ftp'pwd();        
  2724.         
  2725.         $val = &ftp'cwd($dir);
  2726.  
  2727.         # go back to the original directory
  2728.         &ftp'cwd($old_dir) || die "Cannot cd to original remote directory";
  2729.     }
  2730.     return $val;
  2731. }
  2732.  
  2733. # Set file/directory attributes
  2734. sub set_attribs
  2735. {
  2736.     local( $path, $type ) = @_;
  2737.     local( $mode );
  2738.     
  2739.     if( $get_file ){
  2740.         local( $pathi ) = $remote_map{ $path };
  2741.         $mode = $remote_mode[ $pathi ];
  2742.     }
  2743.     else {
  2744.         local( $pathi ) = $local_map{ $path };
  2745.         $mode = $local_mode[ $pathi ];
  2746.     }
  2747.  
  2748.     # If I can't figure out the mode or I'm not copying it
  2749.     # use the default
  2750.     if( !$mode_copy || !$mode ){
  2751.         if( $type eq 'f' ){
  2752.             $mode = $file_mode;
  2753.         }
  2754.         elsif( $type eq 'd' ){
  2755.             $mode = $dir_mode;
  2756.         }
  2757.     }
  2758.  
  2759.     # Convert from octal
  2760.     $mode = oct( $mode ) if $mode =~ /^0/;
  2761.  
  2762.     if( $get_file ){
  2763.         # Change local
  2764.  
  2765.         chmod $mode, $path;
  2766.  
  2767.         if( $user ne '' && $group ne '' ){
  2768.             local( $uid, $gid );
  2769.             if( $user =~ /^\d+$/ ){
  2770.                 # User is just a number - presume it is the uid
  2771.                 $uid = $user;
  2772.             }
  2773.             else {
  2774.                 $uid = (getpwnam( $user ))[ 2 ];
  2775.             }
  2776.             if( $group =~ /\d+$/ ){
  2777.                 # Group is just a number - presume it is the gid
  2778.                 $gid = $group;
  2779.             }
  2780.             else {
  2781.                 $gid = (getgrnam( $group ))[ 2 ];
  2782.             }
  2783.  
  2784.             chown $uid, $gid, $path;
  2785.         }
  2786.     }
  2787.     else {
  2788.         # change the remote file
  2789.         if( $remote_has_chmod ){
  2790.             &ftp'chmod( $path, $mode );
  2791.         }
  2792.     }
  2793. }
  2794.  
  2795.  
  2796. sub get_passwd
  2797. {
  2798.     local( $user ) = @_;
  2799.     local( $pass );
  2800.  
  2801.     # prompt for a password
  2802.     $SIG{ 'INT' } = 'IGNORE';
  2803.     $SIG{ 'QUIT' } = 'IGNORE';
  2804.  
  2805.     system "stty -echo </dev/tty >/dev/tty 2>&1";
  2806.     print "Password for $user: ";
  2807.  
  2808.     $pass = <STDIN>;
  2809.     print "\n";
  2810.     chop( $pass );
  2811.  
  2812.     system "stty echo </dev/tty >/dev/tty 2>&1";
  2813.  
  2814.     $SIG{ 'INT' } = 'DEFAULT';
  2815.     $SIG{ 'QUIT' } = 'DEFAULT';
  2816.     
  2817.     return $pass;
  2818. }
  2819.  
  2820. sub compare_times
  2821. {
  2822.     # Try and allow for time zone changes (eg when a site
  2823.     # switches from daylight saving to non daylight saving)
  2824.     # by ignoring differences of exactly one hour
  2825.  
  2826.     local( $t1, $t2 ) = @_;
  2827.     local( $diff ) = ($t1 > $t2 ? $t1 - $t2 : $t2 - $t1);
  2828.  
  2829.     return ($t1 > $t2) && ($diff != 3600);
  2830. }
  2831.  
  2832. sub create_assocs
  2833. {
  2834.     local( $map );
  2835.  
  2836.     &delete_assocs();
  2837.  
  2838.     &msg( "creating assocs ...\n" ) if $debug > 2;
  2839.     foreach $map ( @assocs ){
  2840.         eval "\$$map = \"\$tmp/$map.$$\"";
  2841.         eval "dbmopen( $map, \$$map, 0644 )";
  2842.     }
  2843.     &msg( "creating assocs done\n" ) if $debug > 2;
  2844. }
  2845.  
  2846. sub delete_assocs
  2847. {
  2848.     local( $map );
  2849.  
  2850.     &msg( "deleting assocs ...\n" ) if $debug > 2;
  2851.     foreach $map ( @assocs ){
  2852.         eval "\$$map = \"\$tmp/$map.$$\"";
  2853.         eval "dbmclose( $map )";
  2854.         &unlink_dbm( eval "\$$map" );
  2855.         eval "\%$map = ()";
  2856.     }
  2857.     &msg( "deleting assocs done\n" ) if $debug > 2;
  2858. }
  2859.  
  2860. sub unlink_dbm
  2861. {
  2862.     local( $file ) = @_;
  2863.     unlink "$file.pag";
  2864.     unlink "$file.dir";
  2865. }
  2866.  
  2867. # Chop the tmp file up
  2868. sub bsplit
  2869. {
  2870.     local( $temp, $dest_path, $time ) = @_;
  2871.     local( $dest_dir ) = "$dest_path-split";
  2872.     local( $bufsiz ) = 512;
  2873.     local( $buffer, $in, $sofar );
  2874.  
  2875.     &msg( "Splitting up $temp into $dest_dir/ ($time)\n" ) if $debug;
  2876.  
  2877.     # Stomp on the original directories
  2878.     &sys( "$rm_prog -rf $dest_dir" );
  2879.  
  2880.     &mkdirs( $dest_dir );
  2881.  
  2882.     local( $index ) = "00";
  2883.     local( $part );
  2884.     open( tmp, $temp ) || die "Cannot open $temp!";
  2885.     $sofar = $split_chunk; # Force a new file
  2886.     while( ($in = sysread( tmp, $buffer, $bufsiz )) > 0 ){
  2887.         if( $sofar >= $split_chunk ){
  2888.             if( $part ){
  2889.                 close( part );
  2890.                 if( $time ){
  2891.                     &set_timestamp( $part, $time );
  2892.                 }
  2893.             }
  2894.             $index++;
  2895.             $part = "$dest_dir/part$index";
  2896.             &msg( "creating $part\n" ) if $debug;
  2897.             open( part, ">$part" ) || die "Cannot create $part";
  2898.             # Make sure to keep this!
  2899.             local( $locali ) = $local_map{ $part };
  2900.             $local_keep[ $locali ] = 1;
  2901.             $sofar = 0;
  2902.         }
  2903.         if( ($out = syswrite( part, $buffer, $in )) != $in ){
  2904.             die "Failed to write data to $part";
  2905.         }
  2906.         $sofar += $in;
  2907.     }
  2908.     close( part );
  2909.     if( $time ){
  2910.         &set_timestamp( $part, $time );
  2911.     }
  2912.     close( tmp );
  2913.  
  2914.     # Generate a readme file about what is in the split directory
  2915.     local( $readme ) = "$dest_dir/README";
  2916.     open( readme, ">$readme" ) || die "Cannot create $readme";
  2917.     print readme "This directory contains a splitup version of $dest_path\n";
  2918.     print readme "to recreate the original simply concatenate all the\n";
  2919.     print readme "parts back together.\n\nChecksums are:\n\n";
  2920.     close readme;
  2921.     &sys( "(cd $dest_dir ; $sum_prog part* ) >> $readme" );
  2922. }
  2923.  
  2924. sub sys
  2925. {
  2926.     local( $com ) = @_;
  2927.     &msg( "$com\n" ) if $debug > 2;
  2928.     system( $com );
  2929. }
  2930.  
  2931. # Set up an associative array given all an array of keys.
  2932. # @fred = ( 'a' );
  2933. # &set_assoc_from_array( *fred )
  2934. # Creates => $fred{ 'a' } = 1
  2935. #
  2936. sub set_assoc_from_array
  2937. {
  2938.     local( *things ) = @_;
  2939.     foreach $thing ( @things ){
  2940.         $things{ $thing } = 1;
  2941.     }
  2942. }
  2943.  
  2944. sub find_prog
  2945. {
  2946.     local( $prog ) = @_;
  2947.     local( $path ) = $ENV{ 'PATH' } . ':' . $extra_path;
  2948.     
  2949.     foreach $dir ( split( /:/, $path ) ){
  2950.         local( $path ) = "$dir/$prog";
  2951.         if( -x $path ){
  2952.             return $path;
  2953.         }
  2954.     }
  2955.     return '';
  2956. }
  2957.  
  2958. sub real_dir_from_path
  2959. {
  2960.     local( $program ) = @_;
  2961.     local( @prog_path ) = split( m:/: , $program );    # dir collection
  2962.     local( $dir );
  2963.  
  2964.     while( -l $program ){                # follow symlink
  2965.         $program = readlink( $program );
  2966.         if( $program =~ m:^/: ){        # full path?
  2967.             @prog_path = ();        # start dir collection anew
  2968.         }
  2969.         else {
  2970.             pop( @prog_path );        # discard file name
  2971.         }
  2972.         push( @prog_path, split( m:/:, $program ) );# add new parts
  2973.         $program = join( '/', @prog_path );  # might be a symlink again...
  2974.     }
  2975.     pop( @prog_path );
  2976.     $dir = join( '/', @prog_path );
  2977.  
  2978.     if( ! $dir ){
  2979.         $dir = '.';
  2980.     }
  2981.     
  2982.     return $dir;
  2983. }
  2984.  
  2985. sub msg
  2986. {
  2987.     local( $todo, $msg );
  2988.  
  2989.     if( $#_ == 1 ){
  2990.         ($todo, $msg) = @_;
  2991.     }
  2992.     else {
  2993.         $todo = 0;
  2994.         $msg = @_[ 0 ];
  2995.     }
  2996.  
  2997.     # Assign to $0 so when you do a 'ps' it says this!
  2998.     if( $package ){
  2999.         $0 =  "mirror $package:$site:$remote_dir $msg";
  3000.     }
  3001.     else {
  3002.         $0 = "mirror $msg";
  3003.     }
  3004.  
  3005.     if( $todo & $log ){
  3006.         push( @log, $msg );
  3007.     }
  3008. # Not sure about this one.  always print the message even if its a log msg.
  3009. #    else {
  3010.         print $msg;
  3011. #    }
  3012. }
  3013.  
  3014. sub to_bytes
  3015. {
  3016.     local( $size ) = @_;
  3017.     if( $size =~ /^(\d+)\s*(k|b|m)s*$/i ){
  3018.         $size = $1;
  3019.         if( $2 =~ /[mM]/ ){
  3020.             $size *= (1024*1024);
  3021.         }
  3022.         elsif( $2 =~ /[bB]/ ){
  3023.             $size *= 512;
  3024.         }
  3025.         elsif( $2 =~ /[kK]/ ){
  3026.             $size *= 1024;
  3027.         }
  3028.     }
  3029.     return $size;
  3030. }
  3031.  
  3032. # Given a unix filename map it into a vms name.
  3033. # $kind is 'f' for files and 'd' for directories
  3034. sub unix2vms
  3035. {
  3036.     local( $v, $kind ) = @_;
  3037.  
  3038.     if( $v eq '.' || $v eq '/' ){
  3039.         return "[]";
  3040.     }
  3041.  
  3042.     if( $vms_dir ){
  3043.         $v = $vms_dir . '/' . $v;
  3044.     }
  3045.  
  3046.     if( $kind eq 'f' ){
  3047.         # Map a/b/c.txt into [a.b]c.txt
  3048.         if( $v =~ m,(.*)/([^/]+), ){
  3049.             local( $dir, $rest ) = ($1, $2);
  3050.             $dir =~ s,/,.,g;
  3051.             $v = "[$dir]$rest";
  3052.         }
  3053.     }
  3054.     else {
  3055.         # Map a/b/c into [a.b.c]
  3056.         $v =~ s,/,.,g;
  3057.         $v = "[$v]";
  3058.     }
  3059.     return $v;
  3060. }
  3061.  
  3062. sub dirpart
  3063. {
  3064.     local( $path ) = @_;
  3065.     if( $path =~ m:/: ){
  3066.         $path =~ s:^(.*)/[^/]+$:$1:;
  3067.     }
  3068.     else {
  3069.         $path = '.';
  3070.     }
  3071.     return $path;
  3072. }
  3073.  
  3074. # Fix up a package name.
  3075. # strip trailing and leading ws and replace awkward characters
  3076. # This doesn't guarentee a unique filename.
  3077. sub fix_package
  3078. {
  3079.     local( $package ) = @_;
  3080.     $package =~ s:[\s/']:_:g;
  3081.     return $package;
  3082. }
  3083.